diff --git a/cli/pom.xml b/cli/pom.xml
index b6771c7daf..6042680574 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -5,7 +5,7 @@
org.jenkins-ci.main
pom
- 2.94-SNAPSHOT
+ 2.97-SNAPSHOT
cli
diff --git a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
index 8697026508..d5636f3bd7 100644
--- a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
+++ b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
@@ -47,7 +47,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
-@PrepareForTest(CLI.class) // When mocking new operator caller has to be @PreparedForTest, not class itself
+@PrepareForTest({CLI.class, CLIConnectionFactory.class}) // When mocking new operator caller has to be @PreparedForTest, not class itself
public class PrivateKeyProviderTest {
@Test
diff --git a/core/pom.xml b/core/pom.xml
index a4b053eee5..4517bab996 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -29,7 +29,7 @@ THE SOFTWARE.
org.jenkins-ci.main
pom
- 2.94-SNAPSHOT
+ 2.97-SNAPSHOT
jenkins-core
@@ -39,7 +39,7 @@ THE SOFTWARE.
true
- 1.253
+ 1.254
2.5.6.SEC03
2.4.11
@@ -796,25 +796,6 @@ THE SOFTWARE.
-
- org.codehaus.gmaven
- gmaven-plugin
-
-
-
-
- testCompile
-
-
-
-
-
- org.codehaus.groovy
- groovy-all
- ${groovy.version}
-
-
-
org.codehaus.mojo
findbugs-maven-plugin
@@ -889,41 +870,5 @@ THE SOFTWARE.
true
-
-
- cobertura
-
-
-
- org.codehaus.gmaven
- gmaven-plugin
-
-
-
-
- test
-
- execute
-
-
-
- ${project.basedir}/src/build-script
-
- ${project.basedir}/src/build-script/unitTest.groovy
-
-
-
-
-
-
- maven-surefire-plugin
-
-
- true
-
-
-
-
-
diff --git a/core/src/build-script/Cobertura.groovy b/core/src/build-script/Cobertura.groovy
deleted file mode 100644
index 49b773b49b..0000000000
--- a/core/src/build-script/Cobertura.groovy
+++ /dev/null
@@ -1,82 +0,0 @@
-import org.apache.maven.project.MavenProject;
-
-/**
- * Cobertura invoker.
- */
-public class Cobertura {
- private final MavenProject project;
- // maven helper
- private def maven;
- // ant builder
- private def ant;
- /**
- * Cobertura data file.
- */
- private final File ser;
-
- def Cobertura(project, maven, ant, ser) {
- this.project = maven.project;
- this.maven = maven;
- this.ant = ant;
- this.ser =ser;
-
- // define cobertura tasks
- ant.taskdef(resource:"tasks.properties")
- }
-
- // function that ensures that the given directory exists
- private String dir(String dir) {
- new File(project.basedir,dir).mkdirs();
- return dir;
- }
-
- /**
- * Instruments the given class dirs/jars by cobertura
- *
- * @param files
- * List of jar files and class dirs to instrument.
- */
- def instrument(files) {
- ant."cobertura-instrument"(todir:dir("target/cobertura-classes"),datafile:ser) {
- fileset(dir:"target/classes");
- files.each{ fileset(file:it) }
- }
- }
-
- def runTests() {
- ant.junit(fork:true, forkMode:"once", failureproperty:"failed", printsummary:true) {
- classpath {
- junitClasspath()
- }
- batchtest(todir:dir("target/surefire-reports")) {
- fileset(dir:"target/test-classes") {
- include(name:"**/*Test.class")
- }
- formatter(type:"xml")
- }
- sysproperty(key:"net.sourceforge.cobertura.datafile",value:ser)
- sysproperty(key:"hudson.ClassicPluginStrategy.useAntClassLoader",value:"true")
- }
- }
-
- def junitClasspath() {
- ant.pathelement(path: "target/cobertura-classes") // put the instrumented classes first
- ant.fileset(dir:"target/cobertura-classes",includes:"*.jar") // instrumented jar files
- ant.pathelement(path: maven.resolveArtifact("net.sourceforge.cobertura:cobertura:1.9")) // cobertura runtime
- project.getTestClasspathElements().each { ant.pathelement(path: it) } // the rest of the dependencies
- }
-
- def report(dirs) {
- maven.attachArtifact(ser,"ser","cobertura")
- ["html","xml"].each { format ->
- ant."cobertura-report"(format:format,datafile:ser,destdir:dir("target/cobertura-reports"),srcdir:"src/main/java") {
- dirs.each{ fileset(dir:it) }
- }
- }
- }
-
- def makeBuildFailIfTestFail() {
- if(ant.project.getProperty("failed")!=null && !Boolean.getBoolean("testFailureIgnore"))
- throw new Exception("Some unit tests failed");
- }
-}
diff --git a/core/src/build-script/readme.txt b/core/src/build-script/readme.txt
deleted file mode 100644
index 6c53a01026..0000000000
--- a/core/src/build-script/readme.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Parts of the build scripts that are written in Groovy.
-
-IntelliJ needs Groovy files to be in its source directory to provide completion and such,
-so we need this to be in its own directory.
\ No newline at end of file
diff --git a/core/src/build-script/unitTest.groovy b/core/src/build-script/unitTest.groovy
deleted file mode 100644
index 653391aad7..0000000000
--- a/core/src/build-script/unitTest.groovy
+++ /dev/null
@@ -1,11 +0,0 @@
-// run unit tests
-
-ant.project.setBaseDir(project.basedir)
-ser=new File(project.basedir,"target/cobertura.ser"); // store cobertura data file in a module-specific location
-
-cob = new Cobertura(project,maven,ant,ser);
-
-cob.instrument([])
-cob.runTests()
-cob.report([])
-cob.makeBuildFailIfTestFail();
diff --git a/core/src/main/groovy/hudson/util/LoadMonitor.groovy b/core/src/main/groovy/hudson/util/LoadMonitor.groovy
deleted file mode 100644
index 97d7018161..0000000000
--- a/core/src/main/groovy/hudson/util/LoadMonitor.groovy
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package hudson.util
-
-import hudson.model.Computer
-import jenkins.util.Timer
-import jenkins.model.Jenkins
-import hudson.model.Label
-import hudson.model.Queue.BlockedItem
-import hudson.model.Queue.BuildableItem
-import hudson.model.Queue.WaitingItem
-import hudson.triggers.SafeTimerTask
-import java.text.DateFormat
-import java.util.concurrent.TimeUnit
-
-/**
- * Spits out the load information.
- *
- *
- * I'm using this code to design the auto scaling feature.
- * In future this might be useful data to expose to the UI.
- *
- * @author Kohsuke Kawaguchi
- */
-public class LoadMonitorImpl extends SafeTimerTask {
-
- private final File dataFile;
- private List labels;
-
- public LoadMonitorImpl(File dataFile) {
- this.dataFile = dataFile;
- labels = Jenkins.getInstance().labels*.name;
- printHeaders();
- Timer.get().scheduleAtFixedRate(this,0,10*1000, TimeUnit.MILLISECONDS);
- }
-
- private String quote(Object s) { "\"${s}\""; }
-
- protected void printHeaders() {
- def headers = ["# of executors","# of busy executors","BuildableItems in Q","BuildableItem avg wait time"];
- def data = ["timestamp"];
- data += headers;
- data += ["WaitingItems in Q","BlockedItems in Q"];
-
- for( String label : labels)
- data += headers.collect { "${it} (${label}}" }
-
- dataFile.append(data.collect({ quote(it) }).join(",")+"\n");
- }
-
- @Override
- protected void doRun() {
- def now = new Date();
- def data = [];
- data.add(quote(FORMATTER.format(now)));
-
- def h = Jenkins.getInstance();
-
- def items = h.queue.items;
- def filterByType = {Class type -> items.findAll { type.isInstance(it) } }
-
- def builder = {List cs, Closure itemFilter ->
- // number of total executor, number of busy executor
- data.add(cs.sum { it.isOffline() ? 0 : it.numExecutors });
- data.add(cs.sum {Computer c ->
- c.executors.findAll { !it.isIdle() }.size()
- });
-
- // queue statistics
- def is = filterByType(BuildableItem).findAll(itemFilter);
- data.add(is.size());
- data.add(is.sum {BuildableItem bi -> now.time - bi.buildableStartMilliseconds }?:0 / Math.max(1,is.size()) );
- };
-
-
- // for the whole thing
- builder(Arrays.asList(h.computers),{ it->true });
-
- data.add(filterByType(WaitingItem).size());
- data.add(filterByType(BlockedItem).size());
-
- // per label stats
- for (String label : labels) {
- Label l = h.getLabel(label)
- builder(l.nodes.collect { it.toComputer() }) { BuildableItem bi -> bi.task.assignedLabel==l };
- }
-
- dataFile.append(data.join(",")+"\n");
- }
-
- private static final DateFormat FORMATTER = DateFormat.getDateTimeInstance();
-}
-
-new LoadMonitorImpl(new File("/files/hudson/load.txt"));
-
diff --git a/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy b/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy
deleted file mode 100644
index 08cb1c23a3..0000000000
--- a/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy
+++ /dev/null
@@ -1,47 +0,0 @@
-package jenkins.util.groovy
-
-import lib.FormTagLib
-import lib.LayoutTagLib
-import org.kohsuke.stapler.jelly.groovy.JellyBuilder
-import org.kohsuke.stapler.jelly.groovy.Namespace
-import lib.JenkinsTagLib
-
-/**
- * Base class for utility classes for Groovy view scripts
- *
- * Usage from script of a subclass, say ViewHelper:
- *
- * new ViewHelper(delegate).method();
- *
- * see ModularizeViewScript in ui-samples for an example how to use this class.
- *
- * @author Stefan Wolf (wolfs)
- */
-abstract class AbstractGroovyViewModule {
- JellyBuilder builder
- FormTagLib f
- LayoutTagLib l
- JenkinsTagLib t
- Namespace st
-
- public AbstractGroovyViewModule(JellyBuilder b) {
- builder = b
- f= builder.namespace(FormTagLib)
- l=builder.namespace(LayoutTagLib)
- t=builder.namespace(JenkinsTagLib)
- st=builder.namespace("jelly:stapler")
-
- }
-
- def methodMissing(String name, args) {
- builder.invokeMethod(name,args)
- }
-
- def propertyMissing(String name) {
- builder.getProperty(name)
- }
-
- def propertyMissing(String name, value) {
- builder.setProperty(name, value)
- }
-}
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index 197a27f60b..8d1233ab04 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -79,6 +79,7 @@ import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
+import java.nio.file.LinkOption;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
@@ -125,8 +126,10 @@ import org.kohsuke.stapler.Stapler;
import static hudson.FilePath.TarCompression.GZIP;
import static hudson.Util.deleteFile;
+import static hudson.Util.fileToPath;
import static hudson.Util.fixEmpty;
import static hudson.Util.isSymlink;
+
import java.util.Collections;
/**
@@ -1165,7 +1168,7 @@ public final class FilePath implements Serializable {
// following Ant task to avoid possible race condition.
Thread.sleep(10);
- return f.mkdirs() || f.exists();
+ return mkdirs(f) || f.exists();
}
}))
throw new IOException("Failed to mkdirs: "+remote);
@@ -1615,7 +1618,7 @@ public final class FilePath implements Serializable {
if (Util.NATIVE_CHMOD_MODE) {
PosixAPI.jnr().chmod(f.getAbsolutePath(), mask);
} else {
- Files.setPosixFilePermissions(Util.fileToPath(f), Util.modeToPermissions(mask));
+ Files.setPosixFilePermissions(fileToPath(f), Util.modeToPermissions(mask));
}
}
@@ -1962,7 +1965,7 @@ public final class FilePath implements Serializable {
act(new SecureFileCallable() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
- reading(f).renameTo(creating(new File(target.remote)));
+ Files.move(fileToPath(reading(f)), fileToPath(creating(new File(target.remote))), LinkOption.NOFOLLOW_LINKS);
return null;
}
});
@@ -2024,8 +2027,8 @@ public final class FilePath implements Serializable {
File targetFile = new File(target.remote);
File targetDir = targetFile.getParentFile();
filterNonNull().mkdirs(targetDir);
- Files.createDirectories(Util.fileToPath(targetDir));
- Files.copy(Util.fileToPath(reading(f)), Util.fileToPath(writing(targetFile)), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
+ Files.createDirectories(fileToPath(targetDir));
+ Files.copy(fileToPath(reading(f)), fileToPath(writing(targetFile)), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
return null;
}
});
@@ -2957,11 +2960,12 @@ public final class FilePath implements Serializable {
return f;
}
- private boolean mkdirs(File dir) {
+ private boolean mkdirs(File dir) throws IOException {
if (dir.exists()) return false;
filterNonNull().mkdirs(dir);
- return dir.mkdirs();
+ Files.createDirectories(fileToPath(dir));
+ return true;
}
private File mkdirsE(File dir) throws IOException {
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index c9c5e8545b..bf8aca4e47 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -26,6 +26,7 @@
package hudson;
import hudson.model.Slave;
+import hudson.security.*;
import jenkins.util.SystemProperties;
import hudson.cli.CLICommand;
import hudson.console.ConsoleAnnotationDescriptor;
@@ -56,11 +57,6 @@ import hudson.model.View;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.search.SearchableModelObject;
-import hudson.security.AccessControlled;
-import hudson.security.AuthorizationStrategy;
-import hudson.security.GlobalSecurityConfiguration;
-import hudson.security.Permission;
-import hudson.security.SecurityRealm;
import hudson.security.captcha.CaptchaSupport;
import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.Cloud;
@@ -137,6 +133,7 @@ import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu;
+import org.acegisecurity.Authentication;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyTagException;
@@ -1582,7 +1579,7 @@ public class Functions {
* Checks if the current user is anonymous.
*/
public static boolean isAnonymous() {
- return Jenkins.getAuthentication() instanceof AnonymousAuthenticationToken;
+ return ACL.isAnonymous(Jenkins.getAuthentication());
}
/**
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index ab203c20ec..2ff190fb72 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -120,7 +120,6 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -144,6 +143,7 @@ import hudson.util.FormValidation;
import java.io.ByteArrayInputStream;
import java.net.JarURLConnection;
import java.net.URLConnection;
+import java.util.ServiceLoader;
import java.util.jar.JarEntry;
import static java.util.logging.Level.FINE;
@@ -651,7 +651,17 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
continue;
}
- String artifactId = dependencyToken.split(":")[0];
+ String[] artifactIdVersionPair = dependencyToken.split(":");
+ String artifactId = artifactIdVersionPair[0];
+ VersionNumber dependencyVersion = new VersionNumber(artifactIdVersionPair[1]);
+
+ PluginManager manager = Jenkins.getActiveInstance().getPluginManager();
+ VersionNumber installedVersion = manager.getPluginVersion(manager.rootDir, artifactId);
+ if (installedVersion != null && !installedVersion.isOlderThan(dependencyVersion)) {
+ // Do not downgrade dependencies that are already installed.
+ continue;
+ }
+
URL dependencyURL = context.getResource(fromPath + "/" + artifactId + ".hpi");
if (dependencyURL == null) {
@@ -680,9 +690,8 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
*
*/
protected void loadDetachedPlugins() {
- InstallState installState = Jenkins.getActiveInstance().getInstallState();
- if (InstallState.UPGRADE.equals(installState)) {
- VersionNumber lastExecVersion = new VersionNumber(InstallUtil.getLastExecVersion());
+ VersionNumber lastExecVersion = new VersionNumber(InstallUtil.getLastExecVersion());
+ if (lastExecVersion.isNewerThan(InstallUtil.NEW_INSTALL_VERSION) && lastExecVersion.isOlderThan(Jenkins.getVersion())) {
LOGGER.log(INFO, "Upgrading Jenkins. The last running version was {0}. This Jenkins is version {1}.",
new Object[] {lastExecVersion, Jenkins.VERSION});
@@ -1199,7 +1208,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
/**
* Discover all the service provider implementations of the given class,
* via META-INF/services .
+ * @deprecated Use {@link ServiceLoader} instead, or (more commonly) {@link ExtensionList}.
*/
+ @Deprecated
public Collection> discover( Class spi ) {
Set> result = new HashSet>();
diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java
index 8ee5c7390a..1e13ea9957 100644
--- a/core/src/main/java/hudson/Util.java
+++ b/core/src/main/java/hudson/Util.java
@@ -28,7 +28,6 @@ import jenkins.util.SystemProperties;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.TaskListener;
-import hudson.os.PosixAPI;
import hudson.util.QuotedStringTokenizer;
import hudson.util.VariableResolver;
@@ -36,16 +35,12 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
-import org.apache.tools.ant.taskdefs.Chmod;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FileSet;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
-import jnr.posix.FileStat;
-import jnr.posix.POSIX;
-
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
@@ -70,6 +65,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.DosFileAttributes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -257,16 +253,16 @@ public class Util {
*
* @param f
* What to delete. If a directory, it'll need to be empty.
- * @throws IOException if it exists but could not be successfully deleted
+ * @throws IOException if it exists but could not be successfully deleted,
+ * or if it represents an invalid {@link Path}.
*/
private static void tryOnceDeleteFile(File f) throws IOException {
- if (!f.delete()) {
- if(!f.exists())
- // we are trying to delete a file that no longer exists, so this is not an error
- return;
-
+ Path path = fileToPath(f);
+ try {
+ Files.deleteIfExists(path);
+ } catch (IOException e) {
// perhaps this file is read-only?
- makeWritable(f);
+ makeWritable(path);
/*
on Unix both the file and the directory that contains it has to be writable
for a file deletion to be successful. (Confirmed on Solaris 9)
@@ -280,56 +276,47 @@ public class Util {
$ rm x
rm: x not removed: Permission denied
*/
-
- makeWritable(f.getParentFile());
-
- if(!f.delete() && f.exists()) {
- // trouble-shooting.
- try {
- Files.deleteIfExists(f.toPath());
- } catch (InvalidPathException e) {
- throw new IOException(e);
- }
-
+ Path parent = path.getParent();
+ if (parent != null) {
+ makeWritable(parent);
+ }
+ try {
+ Files.deleteIfExists(path);
+ } catch (IOException e2) {
// see https://java.net/projects/hudson/lists/users/archive/2008-05/message/357
// I suspect other processes putting files in this directory
File[] files = f.listFiles();
if(files!=null && files.length>0)
- throw new IOException("Unable to delete " + f.getPath()+" - files in dir: "+Arrays.asList(files));
- throw new IOException("Unable to delete " + f.getPath());
+ throw new IOException("Unable to delete " + f.getPath()+" - files in dir: "+Arrays.asList(files), e2);
+ throw e2;
}
}
}
/**
- * Makes the given file writable by any means possible.
+ * Makes the file at the given path writable by any means possible.
*/
- private static void makeWritable(@Nonnull File f) {
- if (f.setWritable(true)) {
- return;
- }
- // TODO do we still need to try anything else?
-
- // try chmod. this becomes no-op if this is not Unix.
- try {
- Chmod chmod = new Chmod();
- chmod.setProject(new Project());
- chmod.setFile(f);
- chmod.setPerm("u+w");
- chmod.execute();
- } catch (BuildException e) {
- LOGGER.log(Level.INFO,"Failed to chmod "+f,e);
- }
-
- try {// try libc chmod
- POSIX posix = PosixAPI.jnr();
- String path = f.getAbsolutePath();
- FileStat stat = posix.stat(path);
- posix.chmod(path, stat.mode()|0200); // u+w
- } catch (Throwable t) {
- LOGGER.log(Level.FINE,"Failed to chmod(2) "+f,t);
+ private static void makeWritable(@Nonnull Path path) throws IOException {
+ if (!Functions.isWindows()) {
+ try {
+ PosixFileAttributes attrs = Files.readAttributes(path, PosixFileAttributes.class);
+ Set newPermissions = ((PosixFileAttributes)attrs).permissions();
+ newPermissions.add(PosixFilePermission.OWNER_WRITE);
+ Files.setPosixFilePermissions(path, newPermissions);
+ return;
+ } catch (NoSuchFileException e) {
+ return;
+ } catch (UnsupportedOperationException e) {
+ // PosixFileAttributes not supported, fall back to old IO.
+ }
}
+ /**
+ * We intentionally do not check the return code of setWritable, because if it
+ * is false we prefer to rethrow the exception thrown by Files.deleteIfExists,
+ * which will have a more useful message than something we make up here.
+ */
+ path.toFile().setWritable(true);
}
/**
diff --git a/core/src/main/java/hudson/cli/CLICommand.java b/core/src/main/java/hudson/cli/CLICommand.java
index d507662037..5efbaf3a33 100644
--- a/core/src/main/java/hudson/cli/CLICommand.java
+++ b/core/src/main/java/hudson/cli/CLICommand.java
@@ -30,6 +30,8 @@ import hudson.ExtensionPoint;
import hudson.cli.declarative.CLIMethod;
import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson;
import hudson.Functions;
+import hudson.security.ACL;
+import jenkins.security.SecurityListener;
import jenkins.util.SystemProperties;
import hudson.cli.declarative.OptionHandlerExtension;
import jenkins.model.Jenkins;
@@ -44,6 +46,8 @@ import org.acegisecurity.Authentication;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
+import org.acegisecurity.userdetails.User;
+import org.acegisecurity.userdetails.UserDetails;
import org.apache.commons.discovery.ResourceClassIterator;
import org.apache.commons.discovery.ResourceNameIterator;
import org.apache.commons.discovery.resource.ClassLoaders;
@@ -344,8 +348,16 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
@Deprecated
protected Authentication loadStoredAuthentication() throws InterruptedException {
try {
- if (channel!=null)
- return new ClientAuthenticationCache(channel).get();
+ if (channel!=null){
+ Authentication authLoadedFromCache = new ClientAuthenticationCache(channel).get();
+
+ if(!ACL.isAnonymous(authLoadedFromCache)){
+ UserDetails userDetails = new CLIUserDetails(authLoadedFromCache);
+ SecurityListener.fireAuthenticated(userDetails);
+ }
+
+ return authLoadedFromCache;
+ }
} catch (IOException e) {
stderr.println("Failed to access the stored credential");
Functions.printStackTrace(e, stderr); // recover
@@ -642,4 +654,16 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
}
}
}
+
+ /**
+ * User details loaded from the CLI {@link ClientAuthenticationCache}
+ * The user is never anonymous since it must be authenticated to be stored in the cache
+ */
+ @Deprecated
+ @Restricted(NoExternalUse.class)
+ private static class CLIUserDetails extends User {
+ private CLIUserDetails(Authentication auth) {
+ super(auth.getName(), "", true, true, true, true, auth.getAuthorities());
+ }
+ }
}
diff --git a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java b/core/src/main/java/hudson/cli/ClientAuthenticationCache.java
index cf5b5c113c..48b96f4686 100644
--- a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java
+++ b/core/src/main/java/hudson/cli/ClientAuthenticationCache.java
@@ -22,6 +22,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.security.HMACConfidentialKey;
+import javax.annotation.Nonnull;
+
/**
* Represents the authentication credential store of the CLI client.
*
@@ -73,7 +75,7 @@ public class ClientAuthenticationCache implements Serializable {
*
* @return {@link jenkins.model.Jenkins#ANONYMOUS} if no such credential is found, or if the stored credential is invalid.
*/
- public Authentication get() {
+ public @Nonnull Authentication get() {
Jenkins h = Jenkins.getActiveInstance();
String val = props.getProperty(getPropertyKey());
if (val == null) {
@@ -100,6 +102,7 @@ public class ClientAuthenticationCache implements Serializable {
LOGGER.log(Level.FINER, "Loaded stored CLI authentication for {0}", username);
return new UsernamePasswordAuthenticationToken(u.getUsername(), "", u.getAuthorities());
} catch (AuthenticationException | DataAccessException e) {
+ //TODO there is no check to be consistent with User.ALLOW_NON_EXISTENT_USER_TO_LOGIN
LOGGER.log(Level.FINE, "Stored CLI authentication did not correspond to a valid user: " + username, e);
return Jenkins.ANONYMOUS;
}
diff --git a/core/src/main/java/hudson/cli/LoginCommand.java b/core/src/main/java/hudson/cli/LoginCommand.java
index 8920ad4111..55efd9dbcb 100644
--- a/core/src/main/java/hudson/cli/LoginCommand.java
+++ b/core/src/main/java/hudson/cli/LoginCommand.java
@@ -3,6 +3,7 @@ package hudson.cli;
import hudson.Extension;
import java.io.PrintStream;
import jenkins.model.Jenkins;
+import jenkins.security.SecurityListener;
import org.acegisecurity.Authentication;
import org.kohsuke.args4j.CmdLineException;
@@ -45,6 +46,8 @@ public class LoginCommand extends CLICommand {
ClientAuthenticationCache store = new ClientAuthenticationCache(checkChannel());
store.set(a);
+ SecurityListener.fireLoggedIn(a.getName());
+
return 0;
}
diff --git a/core/src/main/java/hudson/cli/LogoutCommand.java b/core/src/main/java/hudson/cli/LogoutCommand.java
index 822c99d84e..8578a6eb04 100644
--- a/core/src/main/java/hudson/cli/LogoutCommand.java
+++ b/core/src/main/java/hudson/cli/LogoutCommand.java
@@ -1,6 +1,9 @@
package hudson.cli;
import hudson.Extension;
+import jenkins.security.SecurityListener;
+import org.acegisecurity.Authentication;
+
import java.io.PrintStream;
/**
@@ -27,7 +30,13 @@ public class LogoutCommand extends CLICommand {
@Override
protected int run() throws Exception {
ClientAuthenticationCache store = new ClientAuthenticationCache(checkChannel());
+
+ Authentication auth = store.get();
+
store.remove();
+
+ SecurityListener.fireLoggedOut(auth.getName());
+
return 0;
}
}
diff --git a/core/src/main/java/hudson/init/InitStrategy.java b/core/src/main/java/hudson/init/InitStrategy.java
index 0d1e037e6a..9d7d287c43 100644
--- a/core/src/main/java/hudson/init/InitStrategy.java
+++ b/core/src/main/java/hudson/init/InitStrategy.java
@@ -17,7 +17,8 @@ import hudson.PluginManager;
import jenkins.util.SystemProperties;
import hudson.util.DirScanner;
import hudson.util.FileVisitor;
-import hudson.util.Service;
+import java.util.Iterator;
+import java.util.ServiceLoader;
/**
* Strategy pattern of the various key decision making during the Jenkins initialization.
@@ -113,11 +114,12 @@ public class InitStrategy {
* Obtains the instance to be used.
*/
public static InitStrategy get(ClassLoader cl) throws IOException {
- List r = Service.loadInstances(cl, InitStrategy.class);
- if (r.isEmpty()) return new InitStrategy(); // default
-
- InitStrategy s = r.get(0);
- LOGGER.fine("Using "+s+" as InitStrategy");
+ Iterator it = ServiceLoader.load(InitStrategy.class, cl).iterator();
+ if (!it.hasNext()) {
+ return new InitStrategy(); // default
+ }
+ InitStrategy s = it.next();
+ LOGGER.log(Level.FINE, "Using {0} as InitStrategy", s);
return s;
}
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index ffee6413d8..b6be75139f 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -407,6 +407,7 @@ public abstract class AbstractProject,R extends A
/**
* Gets the textual representation of the assigned label as it was entered by the user.
*/
+ @Exported(name="labelExpression")
public String getAssignedLabelString() {
if (canRoam || assignedNode==null) return null;
try {
diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java
index ebfb0fc310..7b16918f04 100644
--- a/core/src/main/java/hudson/model/Cause.java
+++ b/core/src/main/java/hudson/model/Cause.java
@@ -402,17 +402,36 @@ public abstract class Cause {
*/
public static class UserIdCause extends Cause {
+ @CheckForNull
private String userId;
+ /**
+ * Constructor, which uses the current {@link User}.
+ */
public UserIdCause() {
User user = User.current();
this.userId = (user == null) ? null : user.getId();
}
+ /**
+ * Constructor.
+ * @param userId User ID. {@code null} if the user is unknown.
+ * @since TODO
+ */
+ public UserIdCause(@CheckForNull String userId) {
+ this.userId = userId;
+ }
+
@Exported(visibility = 3)
+ @CheckForNull
public String getUserId() {
return userId;
}
+
+ @Nonnull
+ private String getUserIdOrUnknown() {
+ return userId != null ? userId : User.getUnknown().getId();
+ }
@Exported(visibility = 3)
public String getUserName() {
@@ -435,8 +454,8 @@ public abstract class Cause {
@Override
public void print(TaskListener listener) {
listener.getLogger().println(Messages.Cause_UserIdCause_ShortDescription(
- // TODO better to use ModelHyperlinkNote.encodeTo(User), or User.getUrl, since it handles URL escaping
- ModelHyperlinkNote.encodeTo("/user/"+getUserId(), getUserName())));
+ // TODO JENKINS-48467 - better to use ModelHyperlinkNote.encodeTo(User), or User.getUrl, since it handles URL escaping
+ ModelHyperlinkNote.encodeTo("/user/"+getUserIdOrUnknown(), getUserName())));
}
@Override
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index f950cce781..3a968f54a9 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -776,6 +776,12 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
return "computer/" + Util.rawEncode(getName()) + "/";
}
+ @Exported
+ public Set getAssignedLabels() {
+ Node node = getNode();
+ return (node != null) ? node.getAssignedLabels() : Collections.EMPTY_SET;
+ }
+
/**
* Returns projects that are tied on this node.
*/
diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java
index cf6fdbff8b..0c26057765 100644
--- a/core/src/main/java/hudson/model/Label.java
+++ b/core/src/main/java/hudson/model/Label.java
@@ -127,7 +127,7 @@ public abstract class Label extends Actionable implements Comparable, Mod
/**
* Alias for {@link #getDisplayName()}.
*/
- @Exported
+ @Exported(visibility=2)
public final String getName() {
return getDisplayName();
}
diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java
index d75ec1b227..418e49aa0d 100644
--- a/core/src/main/java/hudson/model/User.java
+++ b/core/src/main/java/hudson/model/User.java
@@ -326,23 +326,70 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
* @since 1.419
*/
public @Nonnull Authentication impersonate() throws UsernameNotFoundException {
+ return this.impersonate(this.getUserDetailsForImpersonation());
+ }
+
+ /**
+ * This method checks with {@link SecurityRealm} if the user is a valid user that can login to the security realm.
+ * If {@link SecurityRealm} is a kind that does not support querying information about other users, this will
+ * use {@link LastGrantedAuthoritiesProperty} to pick up the granted authorities as of the last time the user has
+ * logged in.
+ *
+ * @return userDetails for the user, in case he's not found but seems legitimate, we provide a userDetails with minimum access
+ *
+ * @throws UsernameNotFoundException
+ * If this user is not a valid user in the backend {@link SecurityRealm}.
+ */
+ public @Nonnull UserDetails getUserDetailsForImpersonation() throws UsernameNotFoundException {
+ ImpersonatingUserDetailsService userDetailsService = new ImpersonatingUserDetailsService(
+ Jenkins.getInstance().getSecurityRealm().getSecurityComponents().userDetails
+ );
+
try {
- UserDetails u = new ImpersonatingUserDetailsService(
- Jenkins.getInstance().getSecurityRealm().getSecurityComponents().userDetails).loadUserByUsername(id);
- return new UsernamePasswordAuthenticationToken(u.getUsername(), "", u.getAuthorities());
+ UserDetails userDetails = userDetailsService.loadUserByUsername(id);
+ LOGGER.log(Level.FINE, "Impersonation of the user {0} was a success", new Object[]{ id });
+ return userDetails;
} catch (UserMayOrMayNotExistException e) {
+ LOGGER.log(Level.FINE, "The user {0} may or may not exist in the SecurityRealm, so we provide minimum access", new Object[]{ id });
// backend can't load information about other users. so use the stored information if available
} catch (UsernameNotFoundException e) {
// if the user no longer exists in the backend, we need to refuse impersonating this user
- if (!ALLOW_NON_EXISTENT_USER_TO_LOGIN)
+ if(ALLOW_NON_EXISTENT_USER_TO_LOGIN){
+ LOGGER.log(Level.FINE, "The user {0} was not found in the SecurityRealm but we are required to let it pass, due to ALLOW_NON_EXISTENT_USER_TO_LOGIN", new Object[]{ id });
+ }else{
+ LOGGER.log(Level.FINE, "The user {0} was not found in the SecurityRealm", new Object[]{ id });
throw e;
+ }
} catch (DataAccessException e) {
// seems like it's in the same boat as UserMayOrMayNotExistException
+ LOGGER.log(Level.FINE, "The user {0} retrieval just threw a DataAccess exception with msg = {1}, so we provide minimum access", new Object[]{ id, e.getMessage() });
}
+
+ return new LegitimateButUnknownUserDetails(id);
+ }
- // seems like a legitimate user we have no idea about. proceed with minimum access
- return new UsernamePasswordAuthenticationToken(id, "",
- new GrantedAuthority[]{SecurityRealm.AUTHENTICATED_AUTHORITY});
+ /**
+ * Only used for a legitimate user we have no idea about. We give it only minimum access
+ */
+ private static class LegitimateButUnknownUserDetails extends org.acegisecurity.userdetails.User{
+ private LegitimateButUnknownUserDetails(String username) throws IllegalArgumentException {
+ super(
+ username, "",
+ true, true, true, true,
+ new GrantedAuthority[]{SecurityRealm.AUTHENTICATED_AUTHORITY}
+ );
+ }
+ }
+
+ /**
+ * Creates an {@link Authentication} object that represents this user using the given userDetails
+ *
+ * @param userDetails Provided by {@link #getUserDetailsForImpersonation()}.
+ * @see #getUserDetailsForImpersonation()
+ */
+ @Restricted(NoExternalUse.class)
+ public @Nonnull Authentication impersonate(@Nonnull UserDetails userDetails) {
+ return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), "", userDetails.getAuthorities());
}
/**
diff --git a/core/src/main/java/hudson/security/ACL.java b/core/src/main/java/hudson/security/ACL.java
index a2c463d97c..41f8b5a9bc 100644
--- a/core/src/main/java/hudson/security/ACL.java
+++ b/core/src/main/java/hudson/security/ACL.java
@@ -44,6 +44,7 @@ import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.acls.sid.PrincipalSid;
import org.acegisecurity.acls.sid.Sid;
+import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -64,6 +65,9 @@ public abstract class ACL {
*/
public final void checkPermission(@Nonnull Permission p) {
Authentication a = Jenkins.getAuthentication();
+ if (a == SYSTEM) {
+ return;
+ }
if(!hasPermission(a,p))
throw new AccessDeniedException2(a,p);
}
@@ -75,7 +79,11 @@ public abstract class ACL {
* if the user doesn't have the permission.
*/
public final boolean hasPermission(@Nonnull Permission p) {
- return hasPermission(Jenkins.getAuthentication(),p);
+ Authentication a = Jenkins.getAuthentication();
+ if (a == SYSTEM) {
+ return true;
+ }
+ return hasPermission(a, p);
}
/**
@@ -101,6 +109,9 @@ public abstract class ACL {
public final void checkCreatePermission(@Nonnull ItemGroup c,
@Nonnull TopLevelItemDescriptor d) {
Authentication a = Jenkins.getAuthentication();
+ if (a == SYSTEM) {
+ return;
+ }
if (!hasCreatePermission(a, c, d)) {
throw new AccessDeniedException(Messages.AccessDeniedException2_MissingPermission(a.getName(),
Item.CREATE.group.title+"/"+Item.CREATE.name + Item.CREATE + "/" + d.getDisplayName()));
@@ -136,6 +147,9 @@ public abstract class ACL {
public final void checkCreatePermission(@Nonnull ViewGroup c,
@Nonnull ViewDescriptor d) {
Authentication a = Jenkins.getAuthentication();
+ if (a == SYSTEM) {
+ return;
+ }
if (!hasCreatePermission(a, c, d)) {
throw new AccessDeniedException(Messages.AccessDeniedException2_MissingPermission(a.getName(),
View.CREATE.group.title + "/" + View.CREATE.name + View.CREATE + "/" + d.getDisplayName()));
@@ -306,4 +320,13 @@ public abstract class ACL {
return as(user == null ? Jenkins.ANONYMOUS : user.impersonate());
}
+ /**
+ * Checks if the given authentication is anonymous by checking its class.
+ * @see Jenkins#ANONYMOUS
+ * @see AnonymousAuthenticationToken
+ */
+ public static boolean isAnonymous(@Nonnull Authentication authentication) {
+ //TODO use AuthenticationTrustResolver instead to be consistent through the application
+ return authentication instanceof AnonymousAuthenticationToken;
+ }
}
diff --git a/core/src/main/java/hudson/security/AccessControlled.java b/core/src/main/java/hudson/security/AccessControlled.java
index 50f977c349..5a2b246a2b 100644
--- a/core/src/main/java/hudson/security/AccessControlled.java
+++ b/core/src/main/java/hudson/security/AccessControlled.java
@@ -59,6 +59,9 @@ public interface AccessControlled {
* @since FIXME
*/
default boolean hasPermission(@Nonnull Authentication a, @Nonnull Permission permission) {
+ if (a == ACL.SYSTEM) {
+ return true;
+ }
return getACL().hasPermission(a, permission);
}
diff --git a/core/src/main/java/hudson/security/BasicAuthenticationFilter.java b/core/src/main/java/hudson/security/BasicAuthenticationFilter.java
index 11709bcf58..1ec0be214f 100644
--- a/core/src/main/java/hudson/security/BasicAuthenticationFilter.java
+++ b/core/src/main/java/hudson/security/BasicAuthenticationFilter.java
@@ -27,7 +27,10 @@ import hudson.model.User;
import jenkins.model.Jenkins;
import hudson.util.Scrambler;
import jenkins.security.ApiTokenProperty;
+import jenkins.security.SecurityListener;
+import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContextHolder;
+import org.acegisecurity.userdetails.UserDetails;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -138,7 +141,12 @@ public class BasicAuthenticationFilter implements Filter {
User u = User.getById(username, true);
ApiTokenProperty t = u.getProperty(ApiTokenProperty.class);
if (t!=null && t.matchesPassword(password)) {
- SecurityContextHolder.getContext().setAuthentication(u.impersonate());
+ UserDetails userDetails = u.getUserDetailsForImpersonation();
+ Authentication auth = u.impersonate(userDetails);
+
+ SecurityListener.fireAuthenticated(userDetails);
+
+ SecurityContextHolder.getContext().setAuthentication(auth);
try {
chain.doFilter(request,response);
} finally {
diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
index b82e8b70d0..95ff998a2a 100644
--- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
+++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
@@ -41,6 +41,7 @@ import hudson.util.PluginServletFilter;
import hudson.util.Protector;
import hudson.util.Scrambler;
import hudson.util.XStream2;
+import jenkins.security.SecurityListener;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
@@ -258,6 +259,8 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
a = this.getSecurityComponents().manager.authenticate(a);
SecurityContextHolder.getContext().setAuthentication(a);
+ SecurityListener.fireLoggedIn(u.getId());
+
// then back to top
req.getView(this,"success.jelly").forward(req,rsp);
}
diff --git a/core/src/main/java/hudson/security/csrf/CrumbFilter.java b/core/src/main/java/hudson/security/csrf/CrumbFilter.java
index e2b2790ef0..437d220952 100644
--- a/core/src/main/java/hudson/security/csrf/CrumbFilter.java
+++ b/core/src/main/java/hudson/security/csrf/CrumbFilter.java
@@ -8,6 +8,11 @@ package hudson.security.csrf;
import hudson.util.MultipartFormDataParser;
import jenkins.model.Jenkins;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
+import org.kohsuke.MetaInfServices;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.ForwardToView;
+import org.kohsuke.stapler.interceptor.RequirePOST;
import java.io.IOException;
import java.util.Enumeration;
@@ -41,6 +46,15 @@ public class CrumbFilter implements Filter {
return h.getCrumbIssuer();
}
+ @Restricted(NoExternalUse.class)
+ @MetaInfServices
+ public static class ErrorCustomizer implements RequirePOST.ErrorCustomizer {
+ @Override
+ public ForwardToView getForwardView() {
+ return new ForwardToView(CrumbFilter.class, "retry");
+ }
+ }
+
public void init(FilterConfig filterConfig) throws ServletException {
}
diff --git a/core/src/main/java/hudson/util/HistoricalSecrets.java b/core/src/main/java/hudson/util/HistoricalSecrets.java
index 37a6fa39e1..962ab26089 100644
--- a/core/src/main/java/hudson/util/HistoricalSecrets.java
+++ b/core/src/main/java/hudson/util/HistoricalSecrets.java
@@ -80,5 +80,5 @@ public class HistoricalSecrets {
return Util.toAes128Key(secret);
}
- private static final String MAGIC = "::::MAGIC::::";
+ static final String MAGIC = "::::MAGIC::::";
}
diff --git a/core/src/main/java/hudson/util/IOUtils.java b/core/src/main/java/hudson/util/IOUtils.java
index 4168379405..5022dc021f 100644
--- a/core/src/main/java/hudson/util/IOUtils.java
+++ b/core/src/main/java/hudson/util/IOUtils.java
@@ -13,6 +13,8 @@ import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
+import static hudson.Util.fileToPath;
+
/**
* Adds more to commons-io.
*
@@ -51,20 +53,11 @@ public class IOUtils {
* This method returns the 'dir' parameter so that the method call flows better.
*/
public static File mkdirs(File dir) throws IOException {
- if(dir.mkdirs() || dir.exists())
- return dir;
-
- // following Ant task to avoid possible race condition.
try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- // ignore
+ return Files.createDirectories(fileToPath(dir)).toFile();
+ } catch (UnsupportedOperationException e) {
+ throw new IOException(e);
}
-
- if (dir.mkdirs() || dir.exists())
- return dir;
-
- throw new IOException("Failed to create a directory at "+dir);
}
/**
@@ -134,7 +127,7 @@ public class IOUtils {
if (Util.NATIVE_CHMOD_MODE) {
return PosixAPI.jnr().stat(f.getPath()).mode();
} else {
- return Util.permissionsToMode(Files.getPosixFilePermissions(Util.fileToPath(f)));
+ return Util.permissionsToMode(Files.getPosixFilePermissions(fileToPath(f)));
}
} catch (IOException cause) {
PosixException e = new PosixException("Unable to get file permissions", null);
diff --git a/core/src/main/java/hudson/util/PluginServletFilter.java b/core/src/main/java/hudson/util/PluginServletFilter.java
index 9b311aa790..aa2c53973e 100644
--- a/core/src/main/java/hudson/util/PluginServletFilter.java
+++ b/core/src/main/java/hudson/util/PluginServletFilter.java
@@ -113,6 +113,25 @@ public class PluginServletFilter implements Filter, ExtensionPoint {
}
}
+ /**
+ * Checks whether the given filter is already registered in the chain.
+ * @param filter the filter to check.
+ * @return true if the filter is already registered in the chain.
+ * @since FIXME
+ */
+ public static boolean hasFilter(Filter filter) {
+ Jenkins j = Jenkins.getInstanceOrNull();
+ PluginServletFilter container = null;
+ if(j != null) {
+ container = getInstance(j.servletContext);
+ }
+ if (j == null || container == null) {
+ return LEGACY.contains(filter);
+ } else {
+ return container.list.contains(filter);
+ }
+ }
+
public static void removeFilter(Filter filter) throws ServletException {
Jenkins j = Jenkins.getInstanceOrNull();
if (j==null || getInstance(j.servletContext) == null) {
diff --git a/core/src/main/java/hudson/util/Service.java b/core/src/main/java/hudson/util/Service.java
index 6275b07e3f..a979324f33 100644
--- a/core/src/main/java/hudson/util/Service.java
+++ b/core/src/main/java/hudson/util/Service.java
@@ -31,6 +31,7 @@ import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.ArrayList;
+import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
@@ -39,11 +40,10 @@ import static java.util.logging.Level.WARNING;
* Load classes by looking up META-INF/services .
*
* @author Kohsuke Kawaguchi
+ * @deprecated use {@link ServiceLoader} instead.
*/
+@Deprecated
public class Service {
- /**
- * Poorman's clone of JDK6 ServiceLoader.
- */
public static List loadInstances(ClassLoader classLoader, Class type) throws IOException {
List result = new ArrayList();
diff --git a/core/src/main/java/jenkins/InitReactorRunner.java b/core/src/main/java/jenkins/InitReactorRunner.java
index c5c2bfb844..aa6ef83ab7 100644
--- a/core/src/main/java/jenkins/InitReactorRunner.java
+++ b/core/src/main/java/jenkins/InitReactorRunner.java
@@ -1,11 +1,11 @@
package jenkins;
+import com.google.common.collect.Lists;
import jenkins.util.SystemProperties;
import hudson.init.InitMilestone;
import hudson.init.InitReactorListener;
import hudson.util.DaemonThreadFactory;
import hudson.util.NamingThreadFactory;
-import hudson.util.Service;
import jenkins.model.Configuration;
import jenkins.model.Jenkins;
import org.jvnet.hudson.reactor.Milestone;
@@ -16,6 +16,7 @@ import org.jvnet.hudson.reactor.Task;
import java.io.IOException;
import java.util.List;
+import java.util.ServiceLoader;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
@@ -59,7 +60,7 @@ public class InitReactorRunner {
* As such there's no way for plugins to participate into this process.
*/
private ReactorListener buildReactorListener() throws IOException {
- List r = (List) Service.loadInstances(Thread.currentThread().getContextClassLoader(), InitReactorListener.class);
+ List r = Lists.newArrayList(ServiceLoader.load(InitReactorListener.class, Thread.currentThread().getContextClassLoader()));
r.add(new ReactorListener() {
final Level level = Level.parse( Configuration.getStringConfigParameter("initLogLevel", "FINE") );
public void onTaskStarted(Task t) {
diff --git a/core/src/main/java/jenkins/install/InstallState.java b/core/src/main/java/jenkins/install/InstallState.java
index 71790d3c54..939c45f828 100644
--- a/core/src/main/java/jenkins/install/InstallState.java
+++ b/core/src/main/java/jenkins/install/InstallState.java
@@ -49,7 +49,12 @@ public class InstallState implements ExtensionPoint {
* Need InstallState != NEW for tests by default
*/
@Extension
- public static final InstallState UNKNOWN = new InstallState("UNKNOWN", true);
+ public static final InstallState UNKNOWN = new InstallState("UNKNOWN", true) {
+ @Override
+ public void initializeState() {
+ InstallUtil.proceedToNextStateFrom(this);
+ }
+ };
/**
* After any setup / restart / etc. hooks are done, states should be running
@@ -94,7 +99,7 @@ public class InstallState implements ExtensionPoint {
*/
@Extension
public static final InstallState INITIAL_PLUGINS_INSTALLING = new InstallState("INITIAL_PLUGINS_INSTALLING", false);
-
+
/**
* Security setup for a new Jenkins install.
*/
@@ -106,7 +111,7 @@ public class InstallState implements ExtensionPoint {
} catch (Exception e) {
throw new RuntimeException(e);
}
-
+
InstallUtil.proceedToNextStateFrom(INITIAL_SECURITY_SETUP);
}
};
@@ -116,7 +121,7 @@ public class InstallState implements ExtensionPoint {
*/
@Extension
public static final InstallState NEW = new InstallState("NEW", false);
-
+
/**
* Restart of an existing Jenkins install.
*/
diff --git a/core/src/main/java/jenkins/install/InstallUtil.java b/core/src/main/java/jenkins/install/InstallUtil.java
index e4868e95c1..48b8d33ab6 100644
--- a/core/src/main/java/jenkins/install/InstallUtil.java
+++ b/core/src/main/java/jenkins/install/InstallUtil.java
@@ -70,7 +70,8 @@ public class InstallUtil {
private static final Logger LOGGER = Logger.getLogger(InstallUtil.class.getName());
// tests need this to be 1.0
- private static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0");
+ @Restricted(NoExternalUse.class)
+ public static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0");
private static final VersionNumber FORCE_NEW_INSTALL_VERSION = new VersionNumber("0.0");
/**
@@ -92,7 +93,6 @@ public class InstallUtil {
*/
public static void proceedToNextStateFrom(InstallState prior) {
InstallState next = getNextInstallState(prior);
- if (Main.isDevelopmentMode) LOGGER.info("Install state transitioning from: " + prior + " to: " + next);
if (next != null) {
Jenkins.getInstance().setInstallState(next);
}
diff --git a/core/src/main/java/jenkins/install/SetupWizard.java b/core/src/main/java/jenkins/install/SetupWizard.java
index 0f04f903db..f15b468eed 100644
--- a/core/src/main/java/jenkins/install/SetupWizard.java
+++ b/core/src/main/java/jenkins/install/SetupWizard.java
@@ -64,7 +64,6 @@ import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
-import org.jenkinsci.remoting.engine.JnlpProtocol4Handler;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.interceptor.RequirePOST;
@@ -77,6 +76,10 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
@Restricted(NoExternalUse.class)
@Extension
public class SetupWizard extends PageDecorator {
+ public SetupWizard() {
+ checkFilter();
+ }
+
/**
* The security token parameter name
*/
@@ -84,11 +87,6 @@ public class SetupWizard extends PageDecorator {
private static final Logger LOGGER = Logger.getLogger(SetupWizard.class.getName());
- /**
- * Used to determine if this was a new install (vs. an upgrade, restart, or otherwise)
- */
- private static boolean isUsingSecurityToken = false;
-
/**
* Initialize the setup wizard, this will process any current state initializations
*/
@@ -168,17 +166,8 @@ public class SetupWizard extends PageDecorator {
+ "*************************************************************" + ls
+ "*************************************************************" + ls);
}
-
- try {
- PluginServletFilter.addFilter(FORCE_SETUP_WIZARD_FILTER);
- // if we're not using security defaults, we should not show the security token screen
- // users will likely be sent to a login screen instead
- isUsingSecurityToken = isUsingSecurityDefaults();
- } catch (ServletException e) {
- throw new RuntimeException("Unable to add PluginServletFilter for the SetupWizard", e);
- }
}
-
+
try {
// Make sure plugin metadata is up to date
UpdateCenter.updateDefaultSite();
@@ -186,14 +175,34 @@ public class SetupWizard extends PageDecorator {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
}
-
+
+ private void setUpFilter() {
+ try {
+ if (!PluginServletFilter.hasFilter(FORCE_SETUP_WIZARD_FILTER)) {
+ PluginServletFilter.addFilter(FORCE_SETUP_WIZARD_FILTER);
+ }
+ } catch (ServletException e) {
+ throw new RuntimeException("Unable to add PluginServletFilter for the SetupWizard", e);
+ }
+ }
+
+ private void tearDownFilter() {
+ try {
+ if (PluginServletFilter.hasFilter(FORCE_SETUP_WIZARD_FILTER)) {
+ PluginServletFilter.removeFilter(FORCE_SETUP_WIZARD_FILTER);
+ }
+ } catch (ServletException e) {
+ throw new RuntimeException("Unable to remove PluginServletFilter for the SetupWizard", e);
+ }
+ }
+
/**
* Indicates a generated password should be used - e.g. this is a new install, no security realm set up
*/
+ @SuppressWarnings("unused") // used by jelly
public boolean isUsingSecurityToken() {
try {
- return isUsingSecurityToken // only ever show the unlock page if using the security token
- && !Jenkins.getInstance().getInstallState().isSetupComplete()
+ return !Jenkins.getInstance().getInstallState().isSetupComplete()
&& isUsingSecurityDefaults();
} catch (Exception e) {
// ignore
@@ -487,8 +496,6 @@ public class SetupWizard extends PageDecorator {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
InstallUtil.saveLastExecVersion();
setCurrentLevel(Jenkins.getVersion());
- PluginServletFilter.removeFilter(FORCE_SETUP_WIZARD_FILTER);
- isUsingSecurityToken = false; // this should not be considered new anymore
InstallUtil.proceedToNextStateFrom(InstallState.INITIAL_SETUP_COMPLETED);
}
@@ -508,7 +515,28 @@ public class SetupWizard extends PageDecorator {
}
return InstallState.valueOf(name);
}
-
+
+ /**
+ * Called upon install state update.
+ * @param state the new install state.
+ * @since FIXME
+ */
+ public void onInstallStateUpdate(InstallState state) {
+ if (state.isSetupComplete()) {
+ tearDownFilter();
+ } else {
+ setUpFilter();
+ }
+ }
+
+ /**
+ * Returns whether the setup wizard filter is currently registered.
+ * @since FIXME
+ */
+ public boolean hasSetupWizardFilter() {
+ return PluginServletFilter.hasFilter(FORCE_SETUP_WIZARD_FILTER);
+ }
+
/**
* This filter will validate that the security token is provided
*/
@@ -544,4 +572,13 @@ public class SetupWizard extends PageDecorator {
public void destroy() {
}
};
+
+ /**
+ * Sets up the Setup Wizard filter if the current state requires it.
+ */
+ private void checkFilter() {
+ if (!Jenkins.getInstance().getInstallState().isSetupComplete()) {
+ setUpFilter();
+ }
+ }
}
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index 3cbeb92e9f..db7a4005e0 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -327,7 +327,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/**
* The Jenkins instance startup type i.e. NEW, UPGRADE etc
*/
- private transient InstallState installState = InstallState.UNKNOWN;
+ private InstallState installState;
/**
* If we're in the process of an initial setup,
@@ -919,7 +919,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
System.exit(0);
setupWizard = new SetupWizard();
- InstallUtil.proceedToNextStateFrom(InstallState.UNKNOWN);
+ getInstallState().initializeState();
launchTcpSlaveAgentListener();
@@ -1028,9 +1028,12 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
public void setInstallState(@Nonnull InstallState newState) {
InstallState prior = installState;
installState = newState;
- if (!prior.equals(newState)) {
+ LOGGER.log(Main.isDevelopmentMode ? Level.INFO : Level.FINE, "Install state transitioning from: {0} to : {1}", new Object[] { prior, installState });
+ if (!newState.equals(prior)) {
+ getSetupWizard().onInstallStateUpdate(newState);
newState.initializeState();
}
+ saveQuietly();
}
/**
@@ -2587,7 +2590,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
@SuppressWarnings({"unchecked"})
public ExtensionList getExtensionList(Class extensionType) {
- return extensionLists.computeIfAbsent(extensionType, key -> ExtensionList.create(this, key));
+ ExtensionList extensionList = extensionLists.get(extensionType);
+ return extensionList != null ? extensionList : extensionLists.computeIfAbsent(extensionType, key -> ExtensionList.create(this, key));
}
/**
@@ -3064,7 +3068,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
});
}
- g.requires(JOB_LOADED).add("Cleaning up obsolete items deleted from the disk", new Executable() {
+ g.requires(JOB_LOADED).attains(COMPLETED).add("Cleaning up obsolete items deleted from the disk", new Executable() {
public void run(Reactor reactor) throws Exception {
// anything we didn't load from disk, throw them away.
// doing this after loading from disk allows newly loaded items
@@ -3079,7 +3083,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
});
- g.requires(JOB_LOADED).add("Finalizing set up",new Executable() {
+ g.requires(JOB_LOADED).attains(COMPLETED).add("Finalizing set up",new Executable() {
public void run(Reactor session) throws Exception {
rebuildDependencyGraph();
diff --git a/test/src/test/groovy/hudson/GroovyTest.groovy b/core/src/main/java/jenkins/security/ApiCrumbExclusion.java
similarity index 52%
rename from test/src/test/groovy/hudson/GroovyTest.groovy
rename to core/src/main/java/jenkins/security/ApiCrumbExclusion.java
index e512cf56ef..8eea5cee13 100644
--- a/test/src/test/groovy/hudson/GroovyTest.groovy
+++ b/core/src/main/java/jenkins/security/ApiCrumbExclusion.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
- * Copyright (c) 2004-2009, Sun Microsystems, Inc.
+ * Copyright (c) 2017 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
@@ -21,25 +21,33 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-package hudson
+package jenkins.security;
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.JenkinsRule
+import hudson.Extension;
+import hudson.security.csrf.CrumbExclusion;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.DoNotUse;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
/**
- * First groovy test!
- *
- * @author Kohsuke Kawaguchi
+ * JENKINS-22474: Makes API Token calls bypass CSRF protection to ease usage
*/
-public class GroovyTest {
-
- @Rule
- public JenkinsRule j = new JenkinsRule()
-
- @Test
- void test() {
- def wc = j.createWebClient();
- wc.goTo("");
+@Symbol("apiToken")
+@Extension
+@Restricted(DoNotUse.class)
+public class ApiCrumbExclusion extends CrumbExclusion {
+ @Override
+ public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
+ if (Boolean.TRUE.equals(request.getAttribute(BasicHeaderApiTokenAuthenticator.class.getName()))) {
+ chain.doFilter(request, response);
+ return true;
+ }
+ return false;
}
}
diff --git a/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java b/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java
index a192dddc5f..345281fdce 100644
--- a/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java
+++ b/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java
@@ -3,6 +3,7 @@ package jenkins.security;
import hudson.Extension;
import hudson.model.User;
import org.acegisecurity.Authentication;
+import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.springframework.dao.DataAccessException;
@@ -21,14 +22,22 @@ import static java.util.logging.Level.*;
*/
@Extension
public class BasicHeaderApiTokenAuthenticator extends BasicHeaderAuthenticator {
+ /**
+ * Note: if the token does not exist or does not match, we do not use {@link SecurityListener#fireFailedToAuthenticate(String)}
+ * because it will be done in the {@link BasicHeaderRealPasswordAuthenticator} in the case the password is not valid either
+ */
@Override
public Authentication authenticate(HttpServletRequest req, HttpServletResponse rsp, String username, String password) throws ServletException {
// attempt to authenticate as API token
User u = User.getById(username, true);
ApiTokenProperty t = u.getProperty(ApiTokenProperty.class);
if (t!=null && t.matchesPassword(password)) {
+ Authentication auth;
try {
- return u.impersonate();
+ UserDetails userDetails = u.getUserDetailsForImpersonation();
+ auth = u.impersonate(userDetails);
+
+ SecurityListener.fireAuthenticated(userDetails);
} catch (UsernameNotFoundException x) {
// The token was valid, but the impersonation failed. This token is clearly not his real password,
// so there's no point in continuing the request processing. Report this error and abort.
@@ -37,6 +46,9 @@ public class BasicHeaderApiTokenAuthenticator extends BasicHeaderAuthenticator {
} catch (DataAccessException x) {
throw new ServletException(x);
}
+
+ req.setAttribute(BasicHeaderApiTokenAuthenticator.class.getName(), true);
+ return auth;
}
return null;
}
diff --git a/core/src/main/java/jenkins/security/ConfidentialStore.java b/core/src/main/java/jenkins/security/ConfidentialStore.java
index 6e79d3d70a..1a8a152f82 100644
--- a/core/src/main/java/jenkins/security/ConfidentialStore.java
+++ b/core/src/main/java/jenkins/security/ConfidentialStore.java
@@ -4,7 +4,6 @@ import hudson.Extension;
import hudson.Lookup;
import hudson.init.InitMilestone;
import hudson.util.Secret;
-import hudson.util.Service;
import jenkins.model.Jenkins;
import org.kohsuke.MetaInfServices;
@@ -12,7 +11,9 @@ import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.security.SecureRandom;
-import java.util.List;
+import java.util.Iterator;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -68,10 +69,11 @@ public abstract class ConfidentialStore {
ConfidentialStore cs = lookup.get(ConfidentialStore.class);
if (cs==null) {
try {
- List r = (List) Service.loadInstances(ConfidentialStore.class.getClassLoader(), ConfidentialStore.class);
- if (!r.isEmpty())
- cs = r.get(0);
- } catch (IOException e) {
+ Iterator it = ServiceLoader.load(ConfidentialStore.class, ConfidentialStore.class.getClassLoader()).iterator();
+ if (it.hasNext()) {
+ cs = it.next();
+ }
+ } catch (ServiceConfigurationError e) {
LOGGER.log(Level.WARNING, "Failed to list up ConfidentialStore implementations",e);
// fall through
}
diff --git a/core/src/main/java/jenkins/security/SecurityListener.java b/core/src/main/java/jenkins/security/SecurityListener.java
index f8c8ca6604..134e992e7f 100644
--- a/core/src/main/java/jenkins/security/SecurityListener.java
+++ b/core/src/main/java/jenkins/security/SecurityListener.java
@@ -45,29 +45,32 @@ public abstract class SecurityListener implements ExtensionPoint {
private static final Logger LOGGER = Logger.getLogger(SecurityListener.class.getName());
/**
- * Fired when a user was successfully authenticated by password.
- * This might be via the web UI, or via REST (not with an API token) or CLI (not with an SSH key).
- * Only {@link AbstractPasswordBasedSecurityRealm}s are considered.
- * @param details details of the newly authenticated user, such as name and groups
+ * Fired when a user was successfully authenticated using credentials. It could be password or any other credentials.
+ * This might be via the web UI, or via REST (using API token or Basic), or CLI (remoting, auth, ssh)
+ * or any other way plugins can propose.
+ * @param details details of the newly authenticated user, such as name and groups.
*/
protected void authenticated(@Nonnull UserDetails details){}
/**
- * Fired when a user tried to authenticate by password but failed.
+ * Fired when a user tried to authenticate but failed.
+ * In case the authentication method uses multiple layers to validate the credentials,
+ * we do fire this event only when even the last layer failed to authenticate.
* @param username the user
* @see #authenticated
*/
protected void failedToAuthenticate(@Nonnull String username){}
/**
- * Fired when a user has logged in via the web UI.
+ * Fired when a user has logged in. Compared to authenticated, there is a notion of storage / cache.
* Would be called after {@link #authenticated}.
+ * It should be called after the {@link org.acegisecurity.context.SecurityContextHolder#getContext()}'s authentication is set.
* @param username the user
*/
protected void loggedIn(@Nonnull String username){}
/**
- * Fired when a user has failed to log in via the web UI.
+ * Fired when a user has failed to log in.
* Would be called after {@link #failedToAuthenticate}.
* @param username the user
*/
diff --git a/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java b/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java
index 096468e7b7..f519e4ecb6 100644
--- a/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java
+++ b/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java
@@ -24,14 +24,10 @@
package jenkins.slaves;
import hudson.Extension;
-import hudson.init.InitMilestone;
-import hudson.init.Initializer;
import hudson.model.AdministrativeMonitor;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import jenkins.AgentProtocol;
import jenkins.model.Jenkins;
@@ -53,8 +49,6 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
@Restricted(NoExternalUse.class)
public class DeprecatedAgentProtocolMonitor extends AdministrativeMonitor {
- private static final Logger LOGGER = Logger.getLogger(DeprecatedAgentProtocolMonitor.class.getName());
-
public DeprecatedAgentProtocolMonitor() {
super();
}
@@ -97,17 +91,4 @@ public class DeprecatedAgentProtocolMonitor extends AdministrativeMonitor {
}
return StringUtils.join(deprecatedProtocols, ',');
}
-
- @Initializer(after = InitMilestone.JOB_LOADED)
- @Restricted(NoExternalUse.class)
- public static void initializerCheck() {
- String protocols = getDeprecatedProtocolsString();
- if(protocols != null) {
- LOGGER.log(Level.WARNING, "This Jenkins instance uses deprecated Remoting protocols: {0}"
- + "It may impact stability of the instance. "
- + "If newer protocol versions are supported by all system components "
- + "(agents, CLI and other clients), "
- + "it is highly recommended to disable the deprecated protocols.", protocols);
- }
- }
}
diff --git a/core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java b/core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java
new file mode 100644
index 0000000000..ccf4720293
--- /dev/null
+++ b/core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java
@@ -0,0 +1,48 @@
+package jenkins.util.groovy;
+
+import groovy.lang.GroovyObjectSupport;
+import lib.FormTagLib;
+import lib.LayoutTagLib;
+import org.kohsuke.stapler.jelly.groovy.JellyBuilder;
+import org.kohsuke.stapler.jelly.groovy.Namespace;
+import lib.JenkinsTagLib;
+
+/**
+ * Base class for utility classes for Groovy view scripts
+ *
+ * Usage from script of a subclass, say ViewHelper:
+ *
+ * new ViewHelper(delegate).method();
+ *
+ * see ModularizeViewScript in ui-samples for an example how to use
+ * this class.
+ */
+public abstract class AbstractGroovyViewModule extends GroovyObjectSupport {
+
+ public JellyBuilder builder;
+ public FormTagLib f;
+ public LayoutTagLib l;
+ public JenkinsTagLib t;
+ public Namespace st;
+
+ public AbstractGroovyViewModule(JellyBuilder b) {
+ builder = b;
+ f = builder.namespace(FormTagLib.class);
+ l = builder.namespace(LayoutTagLib.class);
+ t = builder.namespace(JenkinsTagLib.class);
+ st = builder.namespace("jelly:stapler");
+ }
+
+ public Object methodMissing(String name, Object args) {
+ return builder.invokeMethod(name, args);
+ }
+
+ public Object propertyMissing(String name) {
+ return builder.getProperty(name);
+ }
+
+ public void propertyMissing(String name, Object value) {
+ builder.setProperty(name, value);
+ }
+
+}
diff --git a/core/src/main/resources/hudson/markup/Messages_zh_CN.properties b/core/src/main/resources/hudson/markup/Messages_zh_CN.properties
new file mode 100644
index 0000000000..9f9f5caf2e
--- /dev/null
+++ b/core/src/main/resources/hudson/markup/Messages_zh_CN.properties
@@ -0,0 +1,23 @@
+# The MIT License
+#
+# Copyright (c) 2017, suren
+#
+# 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.
+
+EscapedMarkupFormatter.DisplayName=\u7EAF\u6587\u672C
diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_CN.properties
index 0703474e44..ce517528d3 100644
--- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_CN.properties
+++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_CN.properties
@@ -21,4 +21,4 @@
# THE SOFTWARE.
Name=\u540D\u79F0
Default\ Value=\u9ED8\u8BA4\u503C
-Description=\u8BF4\u660E
+Description=\u63CF\u8FF0
diff --git a/core/src/main/resources/hudson/model/ChoiceParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/ChoiceParameterDefinition/config_zh_CN.properties
new file mode 100644
index 0000000000..628e80119f
--- /dev/null
+++ b/core/src/main/resources/hudson/model/ChoiceParameterDefinition/config_zh_CN.properties
@@ -0,0 +1,25 @@
+# The MIT License
+#
+# Copyright (c) 2017, suren
+#
+# 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.
+
+Name=\u540D\u79F0
+Choices=\u9009\u9879
+Description=\u63CF\u8FF0
diff --git a/core/src/main/resources/hudson/model/FileParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/FileParameterDefinition/config_zh_CN.properties
new file mode 100644
index 0000000000..15c668fdb4
--- /dev/null
+++ b/core/src/main/resources/hudson/model/FileParameterDefinition/config_zh_CN.properties
@@ -0,0 +1,24 @@
+# The MIT License
+#
+# Copyright (c) 2017, suren
+#
+# 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.
+
+File\ location=\u6587\u4EF6\u8DEF\u5F84
+Description=\u63CF\u8FF0
diff --git a/core/src/main/resources/hudson/model/Job/permalinks_zh_CN.properties b/core/src/main/resources/hudson/model/Job/permalinks_zh_CN.properties
index a743d1c687..f7ce595cdf 100644
--- a/core/src/main/resources/hudson/model/Job/permalinks_zh_CN.properties
+++ b/core/src/main/resources/hudson/model/Job/permalinks_zh_CN.properties
@@ -21,3 +21,5 @@
# THE SOFTWARE.
Permalinks=\u76F8\u5173\u8FDE\u63A5
+Restrict\ where\ this\ project\ can\ be\ run=\u9650\u5236\u9879\u76EE\u7684\u8FD0\u884C\u8282\u70B9
+Label\ Expression=\u6807\u7B7E\u8868\u8FBE\u5F0F
diff --git a/core/src/main/resources/hudson/model/Messages_zh_CN.properties b/core/src/main/resources/hudson/model/Messages_zh_CN.properties
index 379e26ac1d..7c3ccde407 100644
--- a/core/src/main/resources/hudson/model/Messages_zh_CN.properties
+++ b/core/src/main/resources/hudson/model/Messages_zh_CN.properties
@@ -26,6 +26,30 @@ FreeStyleProject.Description=\u8FD9\u662FJenkins\u7684\u4E3B\u8981\u529F\u80FD.J
HealthReport.EmptyString=
+BallColor.Aborted=\u5DF2\u7EC8\u6B62
+BallColor.Disabled=\u7981\u7528
+BallColor.Failed=\u5931\u8D25
+BallColor.InProgress=\u6267\u884C\u4E2D
+BallColor.NotBuilt=\u672A\u6784\u5EFA
+BallColor.Pending=\u7B49\u5F85
+BallColor.Success=\u6210\u529F
+BallColor.Unstable=\u4E0D\u7A33\u5B9A
+
+Item.Permissions.Title=\u4EFB\u52A1
+Item.CREATE.description=\u521B\u5EFA\u65B0\u7684\u4EFB\u52A1\u3002
+Item.DELETE.description=\u5220\u9664\u4EFB\u52A1\u3002
+Item.CONFIGURE.description=\u4FEE\u6539\u4EFB\u52A1\u7684\u914D\u7F6E\u3002
+
+ResultTrend.Aborted=\u5DF2\u7EC8\u6B62
+ResultTrend.Failure=\u5931\u8D25
+ResultTrend.Fixed=\u56FA\u5B9A
+ResultTrend.NotBuilt=\u672A\u6784\u5EFA
+ResultTrend.NowUnstable=\u4E0D\u7A33\u5B9A
+ResultTrend.StillFailing=\u4ECD\u7136\u5931\u8D25
+ResultTrend.StillUnstable=\u4ECD\u7136\u4E0D\u7A33\u5B9A
+ResultTrend.Success=\u6210\u529F
+ResultTrend.Unstable=\u4E0D\u7A33\u5B9A
+
Node.Mode.NORMAL=\u5C3D\u53EF\u80FD\u7684\u4F7F\u7528\u8FD9\u4E2A\u8282\u70B9
Node.Mode.EXCLUSIVE=\u53EA\u5141\u8BB8\u8FD0\u884C\u7ED1\u5B9A\u5230\u8FD9\u53F0\u673A\u5668\u7684Job
@@ -34,3 +58,13 @@ MyView.DisplayName=\u6211\u7684\u89C6\u56FE
ManageJenkinsAction.DisplayName=\u7CFB\u7EDF\u7BA1\u7406
ParametersDefinitionProperty.DisplayName=\u53C2\u6570\u5316\u6784\u5EFA\u8FC7\u7A0B
ListView.DisplayName=\u7B80\u5355\u89C6\u56FE
+
+ParameterAction.DisplayName=\u53C2\u6570
+StringParameterDefinition.DisplayName=\u5B57\u7B26\u53C2\u6570
+TextParameterDefinition.DisplayName=\u6587\u672C\u53C2\u6570
+FileParameterDefinition.DisplayName=\u6587\u4EF6\u53C2\u6570
+BooleanParameterDefinition.DisplayName=\u5E03\u5C14\u503C\u53C2\u6570
+ChoiceParameterDefinition.DisplayName=\u9009\u9879\u53C2\u6570
+ChoiceParameterDefinition.MissingChoices=\u9700\u8981\u9009\u9879\u3002
+RunParameterDefinition.DisplayName=\u8FD0\u884C\u65F6\u53C2\u6570
+PasswordParameterDefinition.DisplayName=\u5BC6\u7801\u53C2\u6570
diff --git a/core/src/main/resources/hudson/model/PasswordParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/PasswordParameterDefinition/config_zh_CN.properties
new file mode 100644
index 0000000000..67cd3a0398
--- /dev/null
+++ b/core/src/main/resources/hudson/model/PasswordParameterDefinition/config_zh_CN.properties
@@ -0,0 +1,25 @@
+# The MIT License
+#
+# Copyright (c) 2017, suren
+#
+# 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.
+
+Name=\u540D\u79F0
+Default\ Value=\u9ED8\u8BA4\u503C
+Description=\u63CF\u8FF0
diff --git a/core/src/main/resources/hudson/model/RunParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/RunParameterDefinition/config_zh_CN.properties
new file mode 100644
index 0000000000..22a181a57a
--- /dev/null
+++ b/core/src/main/resources/hudson/model/RunParameterDefinition/config_zh_CN.properties
@@ -0,0 +1,31 @@
+# The MIT License
+#
+# Copyright (c) 2017, suren
+#
+# 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.
+
+Name=\u540D\u79F0
+Project=\u9879\u76EE
+Description=\u63CF\u8FF0
+
+All\ Builds=\u6240\u6709\u6784\u5EFA
+Successful\ Builds\ Only=\u6210\u529F\u7684\u6784\u5EFA
+Completed\ Builds\ Only=\u6267\u884C\u5B8C\u6210\u7684\u6784\u5EFA
+Stable\ Builds\ Only=\u7A33\u5B9A\u7684\u6784\u5EFA
+
diff --git a/core/src/main/resources/hudson/model/StringParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/StringParameterDefinition/config_zh_CN.properties
index 43c49864a8..f051916103 100644
--- a/core/src/main/resources/hudson/model/StringParameterDefinition/config_zh_CN.properties
+++ b/core/src/main/resources/hudson/model/StringParameterDefinition/config_zh_CN.properties
@@ -2,4 +2,5 @@
Default\ Value=\u9ED8\u8BA4\u503C
Description=\u63CF\u8FF0
-Name=\u540D\u5B57
+Name=\u540D\u79F0
+Trim\ the\ string=\u6E05\u9664\u7A7A\u767D\u5B57\u7B26
diff --git a/core/src/main/resources/hudson/model/TextParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/TextParameterDefinition/config_zh_CN.properties
new file mode 100644
index 0000000000..67cd3a0398
--- /dev/null
+++ b/core/src/main/resources/hudson/model/TextParameterDefinition/config_zh_CN.properties
@@ -0,0 +1,25 @@
+# The MIT License
+#
+# Copyright (c) 2017, suren
+#
+# 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.
+
+Name=\u540D\u79F0
+Default\ Value=\u9ED8\u8BA4\u503C
+Description=\u63CF\u8FF0
diff --git a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly
new file mode 100644
index 0000000000..70e615b78e
--- /dev/null
+++ b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+ ${%This URL requires POST}
+
+ ${%blurb}
+
+ ${requestURL}
+ ${%warning}
+
+
+
+
+
+
diff --git a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties
new file mode 100644
index 0000000000..0e47fdc8d5
--- /dev/null
+++ b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties
@@ -0,0 +1,4 @@
+blurb = The URL you're trying to access requires that requests be sent using POST (like a form submission). \
+ The button below allows you to retry accessing this URL using POST. \
+ URL being accessed:
+warning = If you were sent here from an untrusted source, please proceed with caution.
\ No newline at end of file
diff --git a/core/src/main/resources/lib/form/advanced_fr.properties b/core/src/main/resources/lib/form/advanced_fr.properties
index 7f82aecfd4..c998a30a84 100644
--- a/core/src/main/resources/lib/form/advanced_fr.properties
+++ b/core/src/main/resources/lib/form/advanced_fr.properties
@@ -20,5 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-Advanced=Avancé
+Advanced=Avanc\u00E9
customizedFields=Un ou plusieurs champs de ce bloc ont \u00E9t\u00E9 \u00E9dit\u00E9s.
diff --git a/core/src/main/resources/lib/hudson/project/config-assignedLabel_zh_CN.properties b/core/src/main/resources/lib/hudson/project/config-assignedLabel_zh_CN.properties
new file mode 100644
index 0000000000..b9384115a4
--- /dev/null
+++ b/core/src/main/resources/lib/hudson/project/config-assignedLabel_zh_CN.properties
@@ -0,0 +1,24 @@
+# The MIT License
+#
+# Copyright (c) 2017, suren
+#
+# 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.
+
+Restrict\ where\ this\ project\ can\ be\ run=\u9650\u5236\u9879\u76EE\u7684\u8FD0\u884C\u8282\u70B9
+Label\ Expression=\u6807\u7B7E\u8868\u8FBE\u5F0F
diff --git a/core/src/main/resources/lib/hudson/scriptConsole_de.properties b/core/src/main/resources/lib/hudson/scriptConsole_de.properties
index 4db58a7382..6505dca93e 100644
--- a/core/src/main/resources/lib/hudson/scriptConsole_de.properties
+++ b/core/src/main/resources/lib/hudson/scriptConsole_de.properties
@@ -26,7 +26,7 @@ Run=Ausf\u00FChren
description=Geben Sie ein beliebiges Groovy-Skript \
ein und f\u00FChren Sie dieses auf dem Server aus. Dies ist n\u00FCtzlich bei der Fehlersuche und zur Diagnostik. \
Verwenden Sie den println -Befehl, um Ausgaben sichtbar zu machen (wenn Sie System.out \
- verwenden, gehen die Ausgaben auf die Standardausgabe (STDOUT) des Servers, die schwieriger
+ verwenden, gehen die Ausgaben auf die Standardausgabe (STDOUT) des Servers, die schwieriger \
einzusehen ist). Beispiel:
description2=Alle Klassen aller Plugins sind verf\u00FCgbar. jenkins.* , jenkins.model.* , hudson.* sowie hudson.model.* werden standardm\u00E4\u00DFig importiert.
It\ is\ not\ possible\ to\ run\ scripts\ when\ agent\ is\ offline.=Es ist nicht m\u00F6glich, Skripte auf einem Agenten, der offline ist, auszuf\u00FChren
diff --git a/core/src/test/groovy/hudson/util/SecretRewriterTest.groovy b/core/src/test/groovy/hudson/util/SecretRewriterTest.groovy
deleted file mode 100644
index a429bef54e..0000000000
--- a/core/src/test/groovy/hudson/util/SecretRewriterTest.groovy
+++ /dev/null
@@ -1,120 +0,0 @@
-package hudson.util
-
-import com.trilead.ssh2.crypto.Base64
-import hudson.FilePath
-import hudson.Functions
-import jenkins.security.ConfidentialStoreRule
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TemporaryFolder
-
-import javax.crypto.Cipher
-
-import static org.junit.Assume.assumeFalse
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class SecretRewriterTest {
- @Rule
- public MockSecretRule mockSecretRule = new MockSecretRule()
-
- @Rule
- public ConfidentialStoreRule confidentialStoreRule = new ConfidentialStoreRule();
-
- @Rule public TemporaryFolder tmp = new TemporaryFolder()
-
- def FOO_PATTERN = /\{[A-Za-z0-9+\/]+={0,2}}<\/foo>/
- def MSG_PATTERN = /\{[A-Za-z0-9+\/]+={0,2}}<\/msg>/
- def FOO_PATTERN2 = /(\{[A-Za-z0-9+\/]+={0,2}}<\/foo>){2}/
- def ABC_FOO_PATTERN = /\s\{[A-Za-z0-9+\/]+={0,2}}<\/foo>\s<\/abc>/
-
- @Test
- void singleFileRewrite() {
- def o = encryptOld('foobar') // old
- def n = encryptNew('foobar') // new
- roundtrip "${o} ",
- {assert it ==~ FOO_PATTERN}
-
-
- roundtrip "${o} ${o} ",
- {assert it ==~ FOO_PATTERN2}
-
- roundtrip "${n} ",
- {assert it == "${n} "}
-
- roundtrip " thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret ",
- {assert it == "thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret "}
-
- // to be rewritten, it needs to be between a tag
- roundtrip "$o", {assert it == "$o"}
- roundtrip "$o ", {assert it == "$o "}
-
- //
- roundtrip "\n$o \n ", {assert it ==~ ABC_FOO_PATTERN}
- }
-
- void roundtrip(String before, Closure check) {
- def sr = new SecretRewriter(null);
- def f = File.createTempFile("test", "xml", tmp.root)
- f.text = before
- sr.rewrite(f,null)
- check(f.text.replaceAll(System.getProperty("line.separator"), "\n").trim())
- //assert after.replaceAll(System.getProperty("line.separator"), "\n").trim()==f.text.replaceAll(System.getProperty("line.separator"), "\n").trim()
- }
-
- String encryptOld(str) {
- def cipher = Secret.getCipher("AES");
- cipher.init(Cipher.ENCRYPT_MODE, HistoricalSecrets.legacyKey);
- return new String(Base64.encode(cipher.doFinal((str + HistoricalSecrets.MAGIC).getBytes("UTF-8"))))
- }
-
- String encryptNew(str) {
- return Secret.fromString(str).encryptedValue
- }
-
- /**
- * Directory rewrite and recursion detection
- */
- @Test
- void recursionDetection() {
- assumeFalse("Symlinks don't work on Windows very well", Functions.isWindows())
- def sw = new SecretRewriter();
- def st = StreamTaskListener.fromStdout()
-
- def o = encryptOld("Hello world")
- def n = encryptNew("Hello world")
- def payload = "$o "
- def answer = "$n "
-
- // set up some directories with stuff
- def t = tmp.newFolder("t")
- def dirs = ["a", "b", "c", "c/d", "c/d/e"]
- dirs.each { p ->
- def d = new File(t, p)
- d.mkdir()
- new File(d,"foo.xml").text = payload
- }
-
- // stuff outside
- def t2 = tmp.newFolder("t2")
- new File(t2,"foo.xml").text = payload
-
- // some recursions as well as valid symlinks
- new FilePath(t).child("c/symlink").symlinkTo("..",st)
- new FilePath(t).child("b/symlink").symlinkTo(".",st)
- new FilePath(t).child("a/symlink").symlinkTo(t2.absolutePath,st)
-
- assert 6==sw.rewriteRecursive(t, st)
-
- dirs.each { p->
- assert new File(t,"$p/foo.xml").text.trim() ==~ MSG_PATTERN
- }
-
- // t2 is only reachable by following a symlink. this should be covered, too
- assert new File(t2,"foo.xml").text.trim() ==~ MSG_PATTERN
- }
-
-}
diff --git a/core/src/test/groovy/jenkins/security/CryptoConfidentialKeyTest.groovy b/core/src/test/groovy/jenkins/security/CryptoConfidentialKeyTest.groovy
deleted file mode 100644
index e3816a342c..0000000000
--- a/core/src/test/groovy/jenkins/security/CryptoConfidentialKeyTest.groovy
+++ /dev/null
@@ -1,37 +0,0 @@
-package jenkins.security
-
-import org.junit.Rule
-import org.junit.Test
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class CryptoConfidentialKeyTest {
- @Rule
- public ConfidentialStoreRule store = new ConfidentialStoreRule()
-
- def key = new CryptoConfidentialKey("test")
-
- @Test
- void decryptGetsPlainTextBack() {
- ["Hello world","","\u0000"].each { str ->
- assert key.decrypt().doFinal(key.encrypt().doFinal(str.bytes))==str.bytes
- }
- }
-
- @Test
- void multipleEncryptsAreIdempotent() {
- def str = "Hello world".bytes
- assert key.encrypt().doFinal(str)==key.encrypt().doFinal(str)
- }
-
- @Test
- void loadingExistingKey() {
- def key2 = new CryptoConfidentialKey("test") // this will cause the key to be loaded from the disk
- ["Hello world","","\u0000"].each { str ->
- assert key2.decrypt().doFinal(key.encrypt().doFinal(str.bytes))==str.bytes
- }
- }
-}
diff --git a/core/src/test/groovy/jenkins/security/DefaultConfidentialStoreTest.groovy b/core/src/test/groovy/jenkins/security/DefaultConfidentialStoreTest.groovy
deleted file mode 100644
index 83cdaf218c..0000000000
--- a/core/src/test/groovy/jenkins/security/DefaultConfidentialStoreTest.groovy
+++ /dev/null
@@ -1,54 +0,0 @@
-package jenkins.security
-
-import hudson.FilePath
-import hudson.Functions
-import hudson.Util
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-
-/**
- * @author Kohsuke Kawaguchi
- */
-public class DefaultConfidentialStoreTest {
-
- def tmp;
-
- @Before
- void setup() {
- tmp = Util.createTempDir()
- }
-
- @After
- void tearDown() {
- Util.deleteRecursive(tmp)
- }
-
- @Test
- void roundtrip() {
- tmp.deleteDir() // let ConfidentialStore create a directory
-
- def store = new DefaultConfidentialStore(tmp);
- def key = new ConfidentialKey("test") {};
-
- // basic roundtrip
- def str = "Hello world!"
- store.store(key, str.bytes)
- assert new String(store.load(key))==str
-
- // data storage should have some stuff
- assert new File(tmp,"test").exists()
- assert new File(tmp,"master.key").exists()
-
- assert !new File(tmp,"test").text.contains("Hello") // the data shouldn't be a plain text, obviously
-
- if (!Functions.isWindows())
- assert (new FilePath(tmp).mode()&0777) == 0700 // should be read only
-
- // if the master key changes, we should gracefully fail to load the store
- new File(tmp,"master.key").delete()
- def store2 = new DefaultConfidentialStore(tmp)
- assert new File(tmp,"master.key").exists() // we should have a new key now
- assert store2.load(key)==null;
- }
-}
diff --git a/core/src/test/groovy/jenkins/security/HMACConfidentialKeyTest.groovy b/core/src/test/groovy/jenkins/security/HMACConfidentialKeyTest.groovy
deleted file mode 100644
index 118ff5b904..0000000000
--- a/core/src/test/groovy/jenkins/security/HMACConfidentialKeyTest.groovy
+++ /dev/null
@@ -1,39 +0,0 @@
-package jenkins.security
-
-import org.junit.Rule
-import org.junit.Test
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class HMACConfidentialKeyTest {
- @Rule
- public ConfidentialStoreRule store = new ConfidentialStoreRule()
-
- def key = new HMACConfidentialKey("test",16)
-
- @Test
- void basics() {
- def unique = [] as TreeSet
- ["Hello world","","\u0000"].each { str ->
- def mac = key.mac(str)
- unique.add(mac)
- assert mac =~ /[0-9A-Fa-f]{32}/
- assert key.checkMac(str,mac)
- assert !key.checkMac("garbage",mac)
- }
-
- assert unique.size()==3 // make sure all 3 MAC are different
- }
-
- @Test
- void loadingExistingKey() {
- // this second key of the same ID will cause it to load the key from the disk
- def key2 = new HMACConfidentialKey("test",16)
- ["Hello world","","\u0000"].each { str ->
- assert key.mac(str)==key2.mac(str)
- }
- }
-}
diff --git a/core/src/test/groovy/jenkins/security/HexStringConfidentialKeyTest.groovy b/core/src/test/groovy/jenkins/security/HexStringConfidentialKeyTest.groovy
deleted file mode 100644
index 7062d2326f..0000000000
--- a/core/src/test/groovy/jenkins/security/HexStringConfidentialKeyTest.groovy
+++ /dev/null
@@ -1,43 +0,0 @@
-package jenkins.security
-
-import org.junit.Rule
-import org.junit.Test
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class HexStringConfidentialKeyTest {
- @Rule
- public ConfidentialStoreRule store = new ConfidentialStoreRule()
-
- @Test
- void hexStringShouldProduceHexString() {
- def key = new HexStringConfidentialKey("test",8)
- assert key.get() =~ /[A-Fa-f0-9]{8}/
- }
-
- @Test
- void multipleGetsAreIdempotent() {
- def key = new HexStringConfidentialKey("test",8)
- assert key.get()==key.get()
- }
-
- @Test
- void specifyLengthAndMakeSureItTakesEffect() {
- [8,16,32,256].each { n ->
- new HexStringConfidentialKey("test"+n,n).get().length()==n
- }
- }
-
- @Test
- void loadingExistingKey() {
- def key1 = new HexStringConfidentialKey("test",8)
- key1.get() // this causes the ke to be generated
-
- // this second key of the same ID will cause it to load the key from the disk
- def key2 = new HexStringConfidentialKey("test",8)
- assert key1.get()==key2.get()
- }
-}
diff --git a/core/src/test/groovy/jenkins/util/AtmostOneTaskExecutorTest.groovy b/core/src/test/groovy/jenkins/util/AtmostOneTaskExecutorTest.groovy
deleted file mode 100644
index e39addae77..0000000000
--- a/core/src/test/groovy/jenkins/util/AtmostOneTaskExecutorTest.groovy
+++ /dev/null
@@ -1,47 +0,0 @@
-package jenkins.util
-
-import hudson.util.OneShotEvent
-import org.junit.Test
-
-import java.util.concurrent.Callable
-import java.util.concurrent.Executors
-import java.util.concurrent.atomic.AtomicInteger
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class AtmostOneTaskExecutorTest {
- def counter = new AtomicInteger()
-
- def lock = new OneShotEvent()
-
- @Test
- public void doubleBooking() {
- def f1,f2;
-
- def base = Executors.newCachedThreadPool()
- def es = new AtmostOneTaskExecutor(base,
- { ->
- counter.incrementAndGet()
- lock.block()
- } as Callable);
- f1 = es.submit()
- while (counter.get() == 0)
- ; // spin lock until executor gets to the choking point
-
- f2 = es.submit() // this should hang
- Thread.sleep(500) // make sure the 2nd task is hanging
- assert counter.get() == 1
- assert !f2.isDone()
-
- lock.signal() // let the first one go
-
- f1.get(); // first one should complete
-
- // now 2nd one gets going and hits the choke point
- f2.get()
- assert counter.get()==2
- }
-}
diff --git a/core/src/test/java/hudson/UtilTest.java b/core/src/test/java/hudson/UtilTest.java
index 21a8993de4..f1cf614055 100644
--- a/core/src/test/java/hudson/UtilTest.java
+++ b/core/src/test/java/hudson/UtilTest.java
@@ -36,6 +36,9 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.FileSystemException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermissions;
import static org.hamcrest.CoreMatchers.allOf;
@@ -287,12 +290,6 @@ public class UtilTest {
@Test
public void testDeleteFile_onWindows() throws Exception {
Assume.assumeTrue(Functions.isWindows());
- Class> c;
- try {
- c = Class.forName("java.nio.file.FileSystemException");
- } catch (ClassNotFoundException x) {
- throw new AssumptionViolatedException("prior to JDK 7", x);
- }
final int defaultDeletionMax = Util.DELETION_MAX;
try {
File f = tmp.newFile();
@@ -304,7 +301,7 @@ public class UtilTest {
Util.deleteFile(f);
fail("should not have been deletable");
} catch (IOException x) {
- assertThat(calcExceptionHierarchy(x), hasItem(c));
+ assertThat(calcExceptionHierarchy(x), hasItem(FileSystemException.class));
assertThat(x.getMessage(), containsString(f.getPath()));
}
} finally {
@@ -313,6 +310,27 @@ public class UtilTest {
}
}
+ @Test
+ public void testDeleteFileReadOnly() throws Exception {
+ // Removing the calls to Util#makeWritable in Util#tryOnceDeleteFile should cause this test to fail.
+ Path file = tmp.newFolder().toPath().resolve("file.tmp");
+ Files.createDirectories(file.getParent());
+ Files.createFile(file);
+ // Using old IO so the test can run on Windows.
+ file.getParent().toFile().setWritable(false);
+ file.toFile().setWritable(false);
+ Util.deleteFile(file.toFile());
+ assertFalse(Files.exists(file));
+ }
+
+ @Test
+ public void testDeleteFileDoesNotExist() throws Exception {
+ Path file = tmp.newFolder().toPath().resolve("file.tmp");
+ assertFalse(Files.exists(file));
+ // Should not throw an exception.
+ Util.deleteFile(file.toFile());
+ }
+
@Test
public void testDeleteContentsRecursive() throws Exception {
final File dir = tmp.newFolder();
diff --git a/core/src/test/java/hudson/util/SecretRewriterTest.java b/core/src/test/java/hudson/util/SecretRewriterTest.java
new file mode 100644
index 0000000000..5ca97ff060
--- /dev/null
+++ b/core/src/test/java/hudson/util/SecretRewriterTest.java
@@ -0,0 +1,116 @@
+package hudson.util;
+
+import com.trilead.ssh2.crypto.Base64;
+import hudson.FilePath;
+import hudson.Functions;
+import hudson.model.TaskListener;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+import javax.crypto.Cipher;
+import jenkins.security.ConfidentialStoreRule;
+import org.apache.commons.io.FileUtils;
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class SecretRewriterTest {
+
+ @Rule
+ public MockSecretRule mockSecretRule = new MockSecretRule();
+
+ @Rule
+ public ConfidentialStoreRule confidentialStoreRule = new ConfidentialStoreRule();
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ private static final Pattern FOO_PATTERN = Pattern.compile("[{][A-Za-z0-9+/]+={0,2}[}] ");
+ private static final Pattern MSG_PATTERN = Pattern.compile("[{][A-Za-z0-9+/]+={0,2}[}] ");
+ private static final Pattern FOO_PATTERN2 = Pattern.compile("([{][A-Za-z0-9+/]+={0,2}[}] ){2}");
+ private static final Pattern ABC_FOO_PATTERN = Pattern.compile("\\s[{][A-Za-z0-9+/]+={0,2}[}] \\s ");
+
+ @Test
+ public void singleFileRewrite() throws Exception {
+ String o = encryptOld("foobar"); // old
+ String n = encryptNew("foobar"); // new
+ assertTrue(FOO_PATTERN.matcher(roundtrip("" + o + " ")).matches());
+ assertTrue(FOO_PATTERN2.matcher(roundtrip("" + o + " " + o + " ")).matches());
+ assertEquals("" + n + " ", roundtrip("" + n + " "));
+ assertEquals("thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret ", roundtrip(" thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret "));
+ // to be rewritten, it needs to be between a tag
+ assertEquals("" + o, roundtrip("" + o));
+ assertEquals(o + " ", roundtrip(o + " "));
+ assertTrue(ABC_FOO_PATTERN.matcher(roundtrip("\n" + o + " \n ")).matches());
+ }
+
+ private String roundtrip(String before) throws Exception {
+ SecretRewriter sr = new SecretRewriter(null);
+ File f = File.createTempFile("test", "xml", tmp.getRoot());
+ FileUtils.write(f, before);
+ sr.rewrite(f, null);
+ //assert after.replaceAll(System.getProperty("line.separator"), "\n").trim()==f.text.replaceAll(System.getProperty("line.separator"), "\n").trim()
+ return FileUtils.readFileToString(f).replaceAll(System.getProperty("line.separator"), "\n").trim();
+ }
+
+ private String encryptOld(String str) throws Exception {
+ Cipher cipher = Secret.getCipher("AES");
+ cipher.init(Cipher.ENCRYPT_MODE, HistoricalSecrets.getLegacyKey());
+ return new String(Base64.encode(cipher.doFinal((str + HistoricalSecrets.MAGIC).getBytes("UTF-8"))));
+ }
+
+ private String encryptNew(String str) {
+ return Secret.fromString(str).getEncryptedValue();
+ }
+
+ /**
+ * Directory rewrite and recursion detection
+ */
+ @Test
+ public void recursionDetection() throws Exception {
+ assumeFalse("Symlinks don't work on Windows very well", Functions.isWindows());
+ SecretRewriter sw = new SecretRewriter();
+ TaskListener st = StreamTaskListener.fromStdout();
+
+ String o = encryptOld("Hello world");
+ String n = encryptNew("Hello world");
+ String payload = "" + o + " ";
+ String answer = "" + n + " ";
+
+ // set up some directories with stuff
+ File t = tmp.newFolder("t");
+ List dirs = Arrays.asList("a", "b", "c", "c/d", "c/d/e");
+ for (String p : dirs) {
+ File d = new File(t, p);
+ d.mkdir();
+ try {
+ FileUtils.write(new File(d, "foo.xml"), payload);
+ } catch (IOException x) {
+ assert false : x;
+ }
+ }
+
+ // stuff outside
+ File t2 = tmp.newFolder("t2");
+ FileUtils.write(new File(t2, "foo.xml"), payload);
+
+ // some recursions as well as valid symlinks
+ new FilePath(t).child("c/symlink").symlinkTo("..", st);
+ new FilePath(t).child("b/symlink").symlinkTo(".", st);
+ new FilePath(t).child("a/symlink").symlinkTo(t2.getAbsolutePath(), st);
+
+ assertEquals(6, sw.rewriteRecursive(t, st));
+
+ for (String p : dirs) {
+ assertTrue(MSG_PATTERN.matcher(FileUtils.readFileToString(new File(t, p + "/foo.xml")).trim()).matches());
+ }
+
+ // t2 is only reachable by following a symlink. this should be covered, too
+ assertTrue(MSG_PATTERN.matcher(FileUtils.readFileToString(new File(t2, "foo.xml")).trim()).matches());
+ }
+
+}
diff --git a/core/src/test/groovy/hudson/util/SecretTest.groovy b/core/src/test/java/hudson/util/SecretTest.java
similarity index 55%
rename from core/src/test/groovy/hudson/util/SecretTest.groovy
rename to core/src/test/java/hudson/util/SecretTest.java
index 8f39ae7ec0..0b9c13b9b6 100644
--- a/core/src/test/groovy/hudson/util/SecretTest.groovy
+++ b/core/src/test/java/hudson/util/SecretTest.java
@@ -21,51 +21,51 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-package hudson.util
+
+package hudson.util;
import com.trilead.ssh2.crypto.Base64;
-import jenkins.model.Jenkins
+import java.util.Random;
+import java.util.regex.Pattern;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import jenkins.model.Jenkins;
import jenkins.security.ConfidentialStoreRule;
import org.apache.commons.lang.RandomStringUtils;
-import org.junit.Rule
-import org.junit.Test
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
-import java.util.Random;
-import javax.crypto.Cipher
-import java.util.regex.Pattern;
-
-/**
- * @author Kohsuke Kawaguchi
- */
public class SecretTest {
- @Rule
- public ConfidentialStoreRule confidentialStore = new ConfidentialStoreRule()
@Rule
- public MockSecretRule mockSecretRule = new MockSecretRule()
+ public ConfidentialStoreRule confidentialStore = new ConfidentialStoreRule();
- static final Pattern ENCRYPTED_VALUE_PATTERN = Pattern.compile("\\{?[A-Za-z0-9+/]+={0,2}}?");
+ @Rule
+ public MockSecretRule mockSecretRule = new MockSecretRule();
+
+ private static final Pattern ENCRYPTED_VALUE_PATTERN = Pattern.compile("\\{?[A-Za-z0-9+/]+={0,2}}?");
@Test
- void testEncrypt() {
- def secret = Secret.fromString("abc");
- assert "abc"==secret.plainText;
+ public void encrypt() {
+ Secret secret = Secret.fromString("abc");
+ assertEquals("abc", secret.getPlainText());
// make sure we got some encryption going
- println secret.encryptedValue;
- assert !"abc".equals(secret.encryptedValue);
+ assertNotEquals("abc", secret.getEncryptedValue());
// can we round trip?
- assert secret==Secret.fromString(secret.encryptedValue);
+ assertEquals(secret, Secret.fromString(secret.getEncryptedValue()));
//Two consecutive encryption requests of the same object should result in the same encrypted value - SECURITY-304
- assert secret.encryptedValue == secret.encryptedValue
+ assertEquals(secret.getEncryptedValue(), secret.getEncryptedValue());
//Two consecutive encryption requests of different objects with the same value should not result in the same encrypted value - SECURITY-304
- assert secret.encryptedValue != Secret.fromString(secret.plainText).encryptedValue
+ assertNotEquals(secret.getEncryptedValue(), Secret.fromString(secret.getPlainText()).getEncryptedValue());
}
@Test
- void testEncryptedValuePattern() {
+ public void encryptedValuePattern() {
for (int i = 1; i < 100; i++) {
String plaintext = RandomStringUtils.random(new Random().nextInt(i));
String ciphertext = Secret.fromString(plaintext).getEncryptedValue();
@@ -83,19 +83,20 @@ public class SecretTest {
}
@Test
- void testDecrypt() {
- assert "abc"==Secret.toString(Secret.fromString("abc"))
+ public void decrypt() {
+ assertEquals("abc", Secret.toString(Secret.fromString("abc")));
}
@Test
- void testSerialization() {
- def s = Secret.fromString("Mr.Jenkins");
- def xml = Jenkins.XSTREAM.toXML(s);
- assert !xml.contains(s.plainText)
- assert xml ==~ /\{[A-Za-z0-9+\/]+={0,2}}<\/hudson\.util\.Secret>/
+ public void serialization() {
+ Secret s = Secret.fromString("Mr.Jenkins");
+ String xml = Jenkins.XSTREAM.toXML(s);
+ assertThat(xml, not(containsString(s.getPlainText())));
+ // TODO MatchesPattern not available until Hamcrest 2.0
+ assertTrue(xml, xml.matches("[{][A-Za-z0-9+/]+={0,2}[}] "));
- def o = Jenkins.XSTREAM.fromXML(xml);
- assert o==s : xml;
+ Object o = Jenkins.XSTREAM.fromXML(xml);
+ assertEquals(xml, s, o);
}
public static class Foo {
@@ -106,27 +107,28 @@ public class SecretTest {
* Makes sure the serialization form is backward compatible with String.
*/
@Test
- void testCompatibilityFromString() {
- def tagName = Foo.class.name.replace("\$","_-");
- def xml = "<$tagName>secret $tagName>";
- def foo = new Foo();
+ public void testCompatibilityFromString() {
+ String tagName = Foo.class.getName().replace("$", "_-");
+ String xml = "<" + tagName + ">secret " + tagName + ">";
+ Foo foo = new Foo();
Jenkins.XSTREAM.fromXML(xml, foo);
- assert "secret"==Secret.toString(foo.password)
+ assertEquals("secret", Secret.toString(foo.password));
}
/**
* Secret persisted with Jenkins.getSecretKey() should still decrypt OK.
*/
@Test
- void migrationFromLegacyKeyToConfidentialStore() {
- def legacy = HistoricalSecrets.legacyKey
- ["Hello world","","\u0000unprintable"].each { str ->
- def cipher = Secret.getCipher("AES");
+ public void migrationFromLegacyKeyToConfidentialStore() throws Exception {
+ SecretKey legacy = HistoricalSecrets.getLegacyKey();
+ for (String str : new String[] {"Hello world", "", "\u0000unprintable"}) {
+ Cipher cipher = Secret.getCipher("AES");
cipher.init(Cipher.ENCRYPT_MODE, legacy);
- def old = new String(Base64.encode(cipher.doFinal((str + HistoricalSecrets.MAGIC).getBytes("UTF-8"))))
- def s = Secret.fromString(old)
- assert s.plainText==str : "secret by the old key should decrypt"
- assert s.encryptedValue!=old : "but when encrypting, ConfidentialKey should be in use"
+ String old = new String(Base64.encode(cipher.doFinal((str + HistoricalSecrets.MAGIC).getBytes("UTF-8"))));
+ Secret s = Secret.fromString(old);
+ assertEquals("secret by the old key should decrypt", str, s.getPlainText());
+ assertNotEquals("but when encrypting, ConfidentialKey should be in use", old, s.getEncryptedValue());
}
}
+
}
diff --git a/core/src/test/java/hudson/util/TextFileTest.java b/core/src/test/java/hudson/util/TextFileTest.java
new file mode 100644
index 0000000000..cb689a080f
--- /dev/null
+++ b/core/src/test/java/hudson/util/TextFileTest.java
@@ -0,0 +1,97 @@
+package hudson.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class TextFileTest {
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test
+ public void head() throws Exception {
+ File f = tmp.newFile();
+ FileUtils.copyURLToFile(getClass().getResource("ascii.txt"), f);
+
+ TextFile t = new TextFile(f);
+ String first35 = "Lorem ipsum dolor sit amet, consect";
+ assertEquals(35, first35.length());
+ assertEquals(first35, t.head(35));
+ }
+
+ @Test
+ public void shortHead() throws Exception {
+ File f = tmp.newFile();
+ FileUtils.write(f, "hello");
+
+ TextFile t = new TextFile(f);
+ assertEquals("hello", t.head(35));
+ }
+
+ @Test
+ public void tail() throws Exception {
+ File f = tmp.newFile();
+ FileUtils.copyURLToFile(getClass().getResource("ascii.txt"), f);
+
+ TextFile t = new TextFile(f);
+ String tailStr = "la, vitae interdum quam rutrum id." + System.lineSeparator();
+ assertEquals(34 + System.lineSeparator().length(), tailStr.length());
+ assertEquals(tailStr, t.fastTail(tailStr.length()));
+ }
+
+ @Test
+ public void shortTail() throws Exception {
+ File f = tmp.newFile();
+ FileUtils.write(f, "hello");
+
+ TextFile t = new TextFile(f);
+ assertEquals("hello", t.fastTail(35));
+ }
+
+ /**
+ * Shift JIS is a multi-byte character encoding.
+ *
+ * In it, 0x82 0x83 is \u30e2, and 0x83 0x82 is \uFF43. So if aren't
+ * careful, we'll parse the text incorrectly.
+ */
+ @Test
+ public void tailShiftJIS() throws Exception {
+ File f = tmp.newFile();
+
+ TextFile t = new TextFile(f);
+
+ try (OutputStream o = new FileOutputStream(f)) {
+ for (int i = 0; i < 80; i++) {
+ for (int j = 0; j < 40; j++) {
+ o.write(0x83);
+ o.write(0x82);
+ }
+ o.write(0x0A);
+ }
+ }
+
+ String tail = t.fastTail(35, Charset.forName("Shift_JIS"));
+ assertEquals(StringUtils.repeat("\u30e2", 34) + "\n", tail);
+ assertEquals(35, tail.length());
+
+ // add one more byte to force fastTail to read from one byte ahead
+ // between this and the previous case, it should start parsing text incorrectly, until it hits NL
+ // where it comes back in sync
+ try (OutputStream o = new FileOutputStream(f, true)) {
+ o.write(0x0A);
+ }
+
+ tail = t.fastTail(35, Charset.forName("Shift_JIS"));
+ assertEquals(StringUtils.repeat("\u30e2", 33) + "\n\n", tail);
+ assertEquals(35, tail.length());
+ }
+
+}
diff --git a/core/src/test/java/jenkins/security/CryptoConfidentialKeyTest.java b/core/src/test/java/jenkins/security/CryptoConfidentialKeyTest.java
new file mode 100644
index 0000000000..1b302a358b
--- /dev/null
+++ b/core/src/test/java/jenkins/security/CryptoConfidentialKeyTest.java
@@ -0,0 +1,35 @@
+package jenkins.security;
+
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class CryptoConfidentialKeyTest {
+
+ @Rule
+ public ConfidentialStoreRule store = new ConfidentialStoreRule();
+
+ private CryptoConfidentialKey key = new CryptoConfidentialKey("test");
+
+ @Test
+ public void decryptGetsPlainTextBack() throws Exception {
+ for (String str : new String[] {"Hello world", "", "\u0000"}) {
+ assertArrayEquals(str.getBytes(), key.decrypt().doFinal(key.encrypt().doFinal(str.getBytes())));
+ }
+ }
+
+ @Test
+ public void multipleEncryptsAreIdempotent() throws Exception {
+ byte[] str = "Hello world".getBytes();
+ assertArrayEquals(key.encrypt().doFinal(str), key.encrypt().doFinal(str));
+ }
+
+ @Test
+ public void loadingExistingKey() throws Exception {
+ CryptoConfidentialKey key2 = new CryptoConfidentialKey("test"); // this will cause the key to be loaded from the disk
+ for (String str : new String[] {"Hello world", "", "\u0000"}) {
+ assertArrayEquals(str.getBytes(), key2.decrypt().doFinal(key.encrypt().doFinal(str.getBytes())));
+ }
+ }
+
+}
diff --git a/core/src/test/java/jenkins/security/DefaultConfidentialStoreTest.java b/core/src/test/java/jenkins/security/DefaultConfidentialStoreTest.java
new file mode 100644
index 0000000000..539b72ddc4
--- /dev/null
+++ b/core/src/test/java/jenkins/security/DefaultConfidentialStoreTest.java
@@ -0,0 +1,47 @@
+package jenkins.security;
+
+import hudson.FilePath;
+import hudson.Functions;
+import java.io.File;
+import org.apache.commons.io.FileUtils;
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class DefaultConfidentialStoreTest {
+
+ @Rule
+ public TemporaryFolder tmpRule = new TemporaryFolder();
+
+ @Test
+ public void roundtrip() throws Exception {
+ File tmp = new File(tmpRule.getRoot(), "tmp"); // let ConfidentialStore create a directory
+
+ DefaultConfidentialStore store = new DefaultConfidentialStore(tmp);
+ ConfidentialKey key = new ConfidentialKey("test") {};
+
+ // basic roundtrip
+ String str = "Hello world!";
+ store.store(key, str.getBytes());
+ assertEquals(str, new String(store.load(key)));
+
+ // data storage should have some stuff
+ assertTrue(new File(tmp, "test").exists());
+ assertTrue(new File(tmp, "master.key").exists());
+
+ assertThat(FileUtils.readFileToString(new File(tmp, "test")), not(containsString("Hello"))); // the data shouldn't be a plain text, obviously
+
+ if (!Functions.isWindows()) {
+ assertEquals(0700, new FilePath(tmp).mode() & 0777); // should be read only
+ }
+
+ // if the master key changes, we should gracefully fail to load the store
+ new File(tmp, "master.key").delete();
+ DefaultConfidentialStore store2 = new DefaultConfidentialStore(tmp);
+ assertTrue(new File(tmp, "master.key").exists()); // we should have a new key now
+ assertNull(store2.load(key));
+ }
+
+}
diff --git a/core/src/test/java/jenkins/security/HMACConfidentialKeyTest.java b/core/src/test/java/jenkins/security/HMACConfidentialKeyTest.java
new file mode 100644
index 0000000000..93cb94cfdb
--- /dev/null
+++ b/core/src/test/java/jenkins/security/HMACConfidentialKeyTest.java
@@ -0,0 +1,38 @@
+package jenkins.security;
+
+import java.util.Set;
+import java.util.TreeSet;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class HMACConfidentialKeyTest {
+
+ @Rule
+ public ConfidentialStoreRule store = new ConfidentialStoreRule();
+
+ private HMACConfidentialKey key = new HMACConfidentialKey("test", 16);
+
+ @Test
+ public void basics() {
+ Set unique = new TreeSet<>();
+ for (String str : new String[] {"Hello world", "", "\u0000"}) {
+ String mac = key.mac(str);
+ unique.add(mac);
+ assertTrue(mac, mac.matches("[0-9A-Fa-f]{32}"));
+ assertTrue(key.checkMac(str, mac));
+ assertFalse(key.checkMac("garbage", mac));
+ }
+ assertEquals("all 3 MAC are different", 3, unique.size());
+ }
+
+ @Test
+ public void loadingExistingKey() {
+ // this second key of the same ID will cause it to load the key from the disk
+ HMACConfidentialKey key2 = new HMACConfidentialKey("test", 16);
+ for (String str : new String[] {"Hello world", "", "\u0000"}) {
+ assertEquals(key.mac(str), key2.mac(str));
+ }
+ }
+
+}
diff --git a/core/src/test/java/jenkins/security/HexStringConfidentialKeyTest.java b/core/src/test/java/jenkins/security/HexStringConfidentialKeyTest.java
new file mode 100644
index 0000000000..e76ad914f9
--- /dev/null
+++ b/core/src/test/java/jenkins/security/HexStringConfidentialKeyTest.java
@@ -0,0 +1,41 @@
+package jenkins.security;
+
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class HexStringConfidentialKeyTest {
+
+ @Rule
+ public ConfidentialStoreRule store = new ConfidentialStoreRule();
+
+ @Test
+ public void hexStringShouldProduceHexString() {
+ HexStringConfidentialKey key = new HexStringConfidentialKey("test", 8);
+ assertTrue(key.get().matches("[A-Fa-f0-9]{8}"));
+ }
+
+ @Test
+ public void multipleGetsAreIdempotent() {
+ HexStringConfidentialKey key = new HexStringConfidentialKey("test", 8);
+ assertEquals(key.get(), key.get());
+ }
+
+ @Test
+ public void specifyLengthAndMakeSureItTakesEffect() {
+ for (int n : new int[] {8, 16, 32, 256}) {
+ assertEquals(n, new HexStringConfidentialKey("test" + n, n).get().length());
+ }
+ }
+
+ @Test
+ public void loadingExistingKey() {
+ HexStringConfidentialKey key1 = new HexStringConfidentialKey("test", 8);
+ key1.get(); // this causes the ke to be generated
+
+ // this second key of the same ID will cause it to load the key from the disk
+ HexStringConfidentialKey key2 = new HexStringConfidentialKey("test", 8);
+ assertEquals(key1.get(), key2.get());
+ }
+
+}
diff --git a/core/src/test/groovy/jenkins/security/RSAConfidentialKeyTest.groovy b/core/src/test/java/jenkins/security/RSAConfidentialKeyTest.java
similarity index 74%
rename from core/src/test/groovy/jenkins/security/RSAConfidentialKeyTest.groovy
rename to core/src/test/java/jenkins/security/RSAConfidentialKeyTest.java
index ed9ef183fe..7b4513940c 100644
--- a/core/src/test/groovy/jenkins/security/RSAConfidentialKeyTest.groovy
+++ b/core/src/test/java/jenkins/security/RSAConfidentialKeyTest.java
@@ -21,28 +21,26 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-package jenkins.security
+package jenkins.security;
-import org.junit.Rule
-import org.junit.Test
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class RSAConfidentialKeyTest {
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class RSAConfidentialKeyTest {
@Rule
- public ConfidentialStoreRule store = new ConfidentialStoreRule()
+ public ConfidentialStoreRule store = new ConfidentialStoreRule();
- def key = new RSAConfidentialKey("test") {}
+ private RSAConfidentialKey key = new RSAConfidentialKey("test") {};
@Test
- void loadingExistingKey() {
+ public void loadingExistingKey() {
// this second key of the same ID will cause it to load the key from the disk
- def key2 = new RSAConfidentialKey("test") {}
+ RSAConfidentialKey key2 = new RSAConfidentialKey("test") {};
- assert key.privateKey==key2.privateKey;
- assert key.publicKey ==key2.publicKey;
+ assertEquals(key.getPrivateKey(), key2.getPrivateKey());
+ assertEquals(key.getPublicKey(), key2.getPublicKey());
}
+
}
diff --git a/core/src/test/groovy/jenkins/security/RSADigitalSignatureConfidentialKeyTest.groovy b/core/src/test/java/jenkins/security/RSADigitalSignatureConfidentialKeyTest.java
similarity index 65%
rename from core/src/test/groovy/jenkins/security/RSADigitalSignatureConfidentialKeyTest.groovy
rename to core/src/test/java/jenkins/security/RSADigitalSignatureConfidentialKeyTest.java
index 7c31c13365..16e57e117f 100644
--- a/core/src/test/groovy/jenkins/security/RSADigitalSignatureConfidentialKeyTest.groovy
+++ b/core/src/test/java/jenkins/security/RSADigitalSignatureConfidentialKeyTest.java
@@ -21,35 +21,32 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-package jenkins.security
+package jenkins.security;
-import hudson.remoting.Base64
-import org.junit.Rule
-import org.junit.Test
+import hudson.remoting.Base64;
+import java.security.Signature;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
-import java.security.Signature
+public class RSADigitalSignatureConfidentialKeyTest {
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class RSADigitalSignatureConfidentialKeyTest {
@Rule
- public ConfidentialStoreRule store = new ConfidentialStoreRule()
+ public ConfidentialStoreRule store = new ConfidentialStoreRule();
- def key = new RSADigitalSignatureConfidentialKey("test");
+ private final RSADigitalSignatureConfidentialKey key = new RSADigitalSignatureConfidentialKey("test");
@Test
- void dsigSignAndVerify() {
- def plainText = "Hello world"
- def msg = key.sign(plainText);
- println msg;
+ public void dsigSignAndVerify() throws Exception {
+ String plainText = "Hello world";
+ String msg = key.sign(plainText);
+ System.out.println(msg);
- def sig = Signature.getInstance("SHA256withRSA");
- sig.initVerify(key.publicKey);
+ Signature sig = Signature.getInstance("SHA256withRSA");
+ sig.initVerify(key.getPublicKey());
sig.update(plainText.getBytes("UTF-8"));
- assert sig.verify(Base64.decode(msg))
+ assertTrue(sig.verify(Base64.decode(msg)));
}
+
}
diff --git a/core/src/test/java/jenkins/util/AtmostOneTaskExecutorTest.java b/core/src/test/java/jenkins/util/AtmostOneTaskExecutorTest.java
new file mode 100644
index 0000000000..c3e8b8461a
--- /dev/null
+++ b/core/src/test/java/jenkins/util/AtmostOneTaskExecutorTest.java
@@ -0,0 +1,49 @@
+package jenkins.util;
+
+import hudson.util.OneShotEvent;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+public class AtmostOneTaskExecutorTest {
+
+ @SuppressWarnings("empty-statement")
+ @Test
+ public void doubleBooking() throws Exception {
+ AtomicInteger counter = new AtomicInteger();
+ OneShotEvent lock = new OneShotEvent();
+ Future> f1, f2;
+
+ ExecutorService base = Executors.newCachedThreadPool();
+ AtmostOneTaskExecutor> es = new AtmostOneTaskExecutor(base, () -> {
+ counter.incrementAndGet();
+ try {
+ lock.block();
+ } catch (InterruptedException x) {
+ assert false : x;
+ }
+ return null;
+ });
+ f1 = es.submit();
+ while (counter.get() == 0) {
+ // spin lock until executor gets to the choking point
+ }
+
+ f2 = es.submit(); // this should hang
+ Thread.sleep(500); // make sure the 2nd task is hanging
+ assertEquals(1, counter.get());
+ assertFalse(f2.isDone());
+
+ lock.signal(); // let the first one go
+
+ f1.get(); // first one should complete
+
+ // now 2nd one gets going and hits the choke point
+ f2.get();
+ assertEquals(2, counter.get());
+ }
+
+}
diff --git a/test/src/test/resources/hudson/util/ascii.txt b/core/src/test/resources/hudson/util/ascii.txt
similarity index 100%
rename from test/src/test/resources/hudson/util/ascii.txt
rename to core/src/test/resources/hudson/util/ascii.txt
diff --git a/pom.xml b/pom.xml
index 223c8a1ffc..6d88780f12 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,7 @@ THE SOFTWARE.
org.jenkins-ci.main
pom
- 2.94-SNAPSHOT
+ 2.97-SNAPSHOT
pom
Jenkins main module
@@ -94,7 +94,7 @@ THE SOFTWARE.
${skipTests}
3.0.4
true
- 1.11
+ 1.12
${access-modifier.version}
${access-modifier.version}
@@ -153,12 +153,17 @@ THE SOFTWARE.
org.powermock
powermock-module-junit4
- 1.6.2
+ 1.6.6
org.powermock
powermock-api-mockito
- 1.6.2
+ 1.6.6
+
+
+ org.objenesis
+ objenesis
+ 2.6
@@ -339,39 +344,6 @@ THE SOFTWARE.
-
- org.codehaus.gmaven
- gmaven-plugin
- 1.5-jenkins-3
-
-
- org.apache.ant
- ant-junit
- 1.7.0
-
-
- net.sourceforge.cobertura
- cobertura
- 1.9
-
-
-
- javax.servlet
- javax.servlet-api
- 3.1.0
-
-
- org.codehaus.gmaven.runtime
- gmaven-runtime-2.0
- 1.5-jenkins-3
-
-
-
-
- 2.0
-
-
org.apache.maven.plugins
maven-deploy-plugin
@@ -516,10 +488,6 @@ THE SOFTWARE.
antlr-maven-plugin
2.1
-
- org.codehaus.mojo
- cobertura-maven-plugin
-
org.codehaus.mojo
findbugs-maven-plugin
@@ -584,20 +552,6 @@ THE SOFTWARE.
-
-
- org.codehaus.gmaven
- gmaven-plugin
- 1.5-jenkins-1
-
- execute
- testCompile
-
-
-
-
-
-
org.apache.maven.plugins
@@ -722,34 +676,6 @@ THE SOFTWARE.
org.jenkins-ci.tools
maven-jenkins-dev-plugin
-
-
-
- org.codehaus.gmaven
- gmaven-plugin
-
-
-
- generateStubs
- compile
- generateTestStubs
- testCompile
-
-
-
-
-
-
diff --git a/test/pom.xml b/test/pom.xml
index 9dccd5a728..da6245605c 100644
--- a/test/pom.xml
+++ b/test/pom.xml
@@ -28,7 +28,7 @@ THE SOFTWARE.
org.jenkins-ci.main
pom
- 2.94-SNAPSHOT
+ 2.97-SNAPSHOT
test
@@ -207,7 +207,6 @@ THE SOFTWARE.
org.objenesis
objenesis
- 2.5.1
test
@@ -241,34 +240,6 @@ THE SOFTWARE.
${concurrency}
-
- org.codehaus.gmaven
- gmaven-plugin
-
-
-
- default
-
-
- generateTestStubs
- testCompile
-
-
-
-
-
- org.apache.ant
- ant
- 1.8.0
-
-
-
- org.apache.ant
- ant-launcher
- 1.8.0
-
-
-
maven-deploy-plugin
@@ -320,37 +291,9 @@ THE SOFTWARE.
true
4
+ 100
-
-
- cobertura
-
-
-
- org.codehaus.gmaven
- gmaven-plugin
-
-
-
-
- test
-
- execute
-
-
-
-
- ${project.basedir}/../core/src/build-script
-
- ${project.basedir}/src/build-script/unitTest.groovy
-
-
-
-
-
-
-
jacoco
diff --git a/test/src/build-script/unitTest.groovy b/test/src/build-script/unitTest.groovy
deleted file mode 100644
index 73e5a56465..0000000000
--- a/test/src/build-script/unitTest.groovy
+++ /dev/null
@@ -1,16 +0,0 @@
-// run unit tests
-import org.apache.commons.io.FileUtils
-
-ant.project.setBaseDir(project.basedir)
-
-// start from where the core left off, and build from there
-ser=new File(project.basedir,"target/cobertura.ser");
-FileUtils.copyFile(maven.resolveArtifact("${project.groupId}:hudson-core:${project.version}:cobertura:ser"),ser)
-
-cob = new Cobertura(project,maven,ant,ser);
-
-// instrumenting remote.jar causes all native m2 job to fail, because forked Maven doesn't have cobertura runtime
-cob.instrument(["hudson-core"].collect{ m -> maven.resolveArtifact("${project.groupId}:${m}:${project.version}") })
-cob.runTests()
-cob.report(["../core/src/main/java"])
-cob.makeBuildFailIfTestFail();
diff --git a/test/src/test/groovy/hudson/RelativePathTest.groovy b/test/src/test/groovy/hudson/RelativePathTest.groovy
deleted file mode 100644
index 25902f811c..0000000000
--- a/test/src/test/groovy/hudson/RelativePathTest.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-package hudson
-
-import hudson.model.AbstractDescribableImpl
-import hudson.model.Describable
-import hudson.model.Descriptor
-import hudson.util.ListBoxModel
-import org.jvnet.hudson.test.Issue
-import org.jvnet.hudson.test.HudsonTestCase
-import org.jvnet.hudson.test.TestExtension
-import org.kohsuke.stapler.QueryParameter
-
-/**
- * Regression test for JENKINS-18776
- *
- * @author Kohsuke Kawaguchi
- */
-class RelativePathTest extends HudsonTestCase implements Describable {
- @Issue("JENKINS-18776")
- void testRelativePath() {
- // I was having trouble causing annotation processing on test stubs
- jenkins.getDescriptorOrDie(RelativePathTest.class)
- jenkins.getDescriptorOrDie(Model.class)
-
- createWebClient().goTo("/self/");
- assert jenkins.getDescriptorOrDie(Model.class).touched
- }
-
- String getName() {
- return "Alice";
- }
-
- Model getModel() {
- return new Model();
- }
-
- DescriptorImpl getDescriptor() {
- return jenkins.getDescriptorOrDie(getClass());
- }
-
- @TestExtension
- static class DescriptorImpl extends Descriptor {}
-
- static class Model extends AbstractDescribableImpl {
- @TestExtension
- static class DescriptorImpl extends Descriptor {
- boolean touched;
-
- ListBoxModel doFillAbcItems(@RelativePath("..") @QueryParameter String name) {
- assert name=="Alice";
- touched = true;
- return new ListBoxModel().add("foo").add("bar")
- }
- }
- }
-}
diff --git a/test/src/test/groovy/hudson/cli/BuildCommandTest.groovy b/test/src/test/groovy/hudson/cli/BuildCommandTest.groovy
deleted file mode 100644
index eb1582ff59..0000000000
--- a/test/src/test/groovy/hudson/cli/BuildCommandTest.groovy
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2004-2009, Sun Microsystems, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package hudson.cli
-
-import org.apache.commons.io.output.TeeOutputStream
-
-import static org.hamcrest.Matchers.*;
-import static hudson.cli.CLICommandInvoker.Matcher.*;
-import static org.junit.Assert.*
-import hudson.Extension
-
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.Issue
-import org.jvnet.hudson.test.CaptureEnvironmentBuilder
-import org.jvnet.hudson.test.JenkinsRule
-import org.jvnet.hudson.test.TestBuilder
-import org.jvnet.hudson.test.TestExtension
-import org.kohsuke.stapler.StaplerRequest
-
-import hudson.Launcher
-import hudson.cli.CLICommandInvoker;
-import hudson.model.AbstractBuild
-import hudson.model.Action
-import hudson.model.BuildListener
-import hudson.model.Executor
-import hudson.model.FreeStyleProject;
-import hudson.model.ParameterDefinition.ParameterDescriptor
-import hudson.model.ParameterValue
-import hudson.model.ParametersAction
-import hudson.model.ParametersDefinitionProperty
-import hudson.model.Queue.QueueDecisionHandler
-import hudson.model.Queue.Task;
-import hudson.model.SimpleParameterDefinition
-import hudson.model.StringParameterDefinition
-import hudson.model.StringParameterValue
-import hudson.model.labels.LabelAtom
-import hudson.tasks.Shell
-import hudson.util.OneShotEvent
-
-import java.util.concurrent.Executor
-
-import net.sf.json.JSONObject
-
-/**
- * {@link BuildCommand} test.
- *
- * @author Kohsuke Kawaguchi
- */
-public class BuildCommandTest {
-
- @Rule public JenkinsRule j = new JenkinsRule();
-
- /**
- * Tests synchronous execution.
- */
- @Test void sync() {
- def p = j.createFreeStyleProject();
- p.buildersList.add(new Shell("sleep 3"));
-
- def cli = new CLI(j.URL)
- try {
- cli.execute(["build","-s",p.name])
- assertFalse(p.getBuildByNumber(1).isBuilding())
- } finally {
- cli.close();
- }
- }
-
- /**
- * Tests synchronous execution with retried verbose output
- */
- @Test void syncWOutputStreaming() {
- def p = j.createFreeStyleProject();
- p.buildersList.add(new Shell("sleep 3"));
-
- def cli =new CLI(j.URL)
- try {
- cli.execute(["build","-s","-v","-r","5",p.name])
- assertFalse(p.getBuildByNumber(1).isBuilding())
- } finally {
- cli.close();
- }
- }
-
- @Test void parameters() {
- def p = j.createFreeStyleProject();
- p.addProperty(new ParametersDefinitionProperty([new StringParameterDefinition("key",null)]));
-
- def cli = new CLI(j.URL)
- try {
- cli.execute(["build","-s","-p","key=foobar",p.name])
- def b = j.assertBuildStatusSuccess(p.getBuildByNumber(1))
- assertEquals("foobar",b.getAction(ParametersAction.class).getParameter("key").value)
- } finally {
- cli.close();
- }
- }
-
- @Test void defaultParameters() {
- def p = j.createFreeStyleProject();
- p.addProperty(new ParametersDefinitionProperty([new StringParameterDefinition("key","default"), new StringParameterDefinition("key2","default2") ]));
-
- def cli = new CLI(j.URL)
- try {
- cli.execute(["build","-s","-p","key=foobar",p.name])
- def b = j.assertBuildStatusSuccess(p.getBuildByNumber(1))
- assertEquals("foobar",b.getAction(ParametersAction.class).getParameter("key").value)
- assertEquals("default2",b.getAction(ParametersAction.class).getParameter("key2").value)
- } finally {
- cli.close();
- }
- }
-
- // TODO randomly fails: Started test0 #1
- @Test void consoleOutput() {
- def p = j.createFreeStyleProject()
- def cli = new CLI(j.URL)
- try {
- def o = new ByteArrayOutputStream()
- cli.execute(["build","-s","-v",p.name],System.in,new TeeOutputStream(System.out,o),System.err)
- j.assertBuildStatusSuccess(p.getBuildByNumber(1))
- assertTrue(o.toString(), o.toString().contains("Started from command line by anonymous"))
- assertTrue(o.toString().contains("Finished: SUCCESS"))
- } finally {
- cli.close()
- }
- }
-
- // TODO randomly fails: Started test0 #1
- @Test void consoleOutputWhenBuildSchedulingRefused() {
- def p = j.createFreeStyleProject()
- def cli = new CLI(j.URL)
- try {
- def o = new ByteArrayOutputStream()
- cli.execute(["build","-s","-v",p.name],System.in,System.out,new TeeOutputStream(System.err,o))
- assertTrue(o.toString(), o.toString().contains(BuildCommand.BUILD_SCHEDULING_REFUSED))
- } finally {
- cli.close()
- }
- }
- // <=>
- @TestExtension("consoleOutputWhenBuildSchedulingRefused")
- static class UnschedulingVetoer extends QueueDecisionHandler {
- public boolean shouldSchedule(Task task, List actions) {
- return false;
- }
- }
-
- @Test void refuseToBuildDisabledProject() {
-
- def project = j.createFreeStyleProject("the-project");
- project.disable();
- def invoker = new CLICommandInvoker(j, new BuildCommand());
- def result = invoker.invokeWithArgs("the-project");
-
- assertThat(result, failedWith(4));
- assertThat(result.stderr(), containsString("ERROR: Cannot build the-project because it is disabled."));
- assertNull("Project should not be built", project.getBuildByNumber(1));
- }
-
- @Test void refuseToBuildNewlyCopiedProject() {
-
- def original = j.createFreeStyleProject("original");
- def newOne = (FreeStyleProject) j.jenkins.copy(original, "new-one");
- def invoker = new CLICommandInvoker(j, new BuildCommand());
- def result = invoker.invokeWithArgs("new-one");
-
- assertThat(result, failedWith(4));
- assertThat(result.stderr(), containsString("ERROR: Cannot build new-one because its configuration has not been saved."));
- assertNull("Project should not be built", newOne.getBuildByNumber(1));
- }
-
- @Test void correctlyParseMapValuesContainingEqualsSign() {
-
- def project = j.createFreeStyleProject("the-project");
- project.addProperty(new ParametersDefinitionProperty([
- new StringParameterDefinition("expr", null)
- ]));
-
- def invoker = new CLICommandInvoker(j, new BuildCommand());
- def result = invoker.invokeWithArgs("the-project", "-p", "expr=a=b", "-s");
-
- assertThat(result, succeeded());
- assertEquals("a=b", project.getBuildByNumber(1).getBuildVariables().get("expr"));
- }
-
- @Issue("JENKINS-15094")
- @Test public void executorsAliveOnParameterWithNullDefaultValue() throws Exception {
- def slave = j.createSlave();
- FreeStyleProject project = j.createFreeStyleProject("foo");
- project.setAssignedNode(slave);
-
- // Create test parameter with Null default value
- def nullDefaultDefinition = new NullDefaultValueParameterDefinition();
- ParametersDefinitionProperty pdp = new ParametersDefinitionProperty(
- new StringParameterDefinition("string", "defaultValue", "description"),
- nullDefaultDefinition);
- project.addProperty(pdp);
- CaptureEnvironmentBuilder builder = new CaptureEnvironmentBuilder();
- project.getBuildersList().add(builder);
-
- // Warmup
- j.buildAndAssertSuccess(project);
-
- for (def exec : slave.toComputer().getExecutors()) {
- assertTrue("Executor has died before the test start: "+exec, exec.isActive());
- }
-
- // Create CLI & run command
- def invoker = new CLICommandInvoker(j, new BuildCommand());
- def result = invoker.invokeWithArgs("foo","-p","string=value");
- assertThat(result, failedWith(2));
- assertThat(result.stderr(), containsString("ERROR: No default value for the parameter \'FOO\'."));
-
- Thread.sleep(5000); // Give the job 5 seconds to be submitted
- assertNull("Build should not be scheduled", j.jenkins.getQueue().getItem(project));
- assertNull("Build should not be scheduled", project.getBuildByNumber(2));
-
- // Check executors health after a timeout
- for (def exec : slave.toComputer().getExecutors()) {
- assertTrue("Executor is dead: "+exec, exec.isActive());
- }
- }
-
- public static final class NullDefaultValueParameterDefinition extends SimpleParameterDefinition {
-
- /*package*/ NullDefaultValueParameterDefinition() {
- super("FOO", "Always null default value");
- }
-
- @Override
- public ParameterValue createValue(String value) {
- return new StringParameterValue("FOO", "BAR");
- }
-
- @Override
- public ParameterValue createValue(StaplerRequest req, JSONObject jo) {
- return createValue("BAR");
- }
-
- @Override
- public ParameterValue getDefaultParameterValue() {
- return null; // Equals to super.getDefaultParameterValue();
- }
-
- @Extension
- public static class DescriptorImpl extends ParameterDescriptor {}
- }
-}
diff --git a/test/src/test/groovy/hudson/cli/SetBuildParameterCommandTest.groovy b/test/src/test/groovy/hudson/cli/SetBuildParameterCommandTest.groovy
deleted file mode 100644
index dcfba02ed8..0000000000
--- a/test/src/test/groovy/hudson/cli/SetBuildParameterCommandTest.groovy
+++ /dev/null
@@ -1,82 +0,0 @@
-package hudson.cli
-
-import hudson.Functions
-import hudson.Launcher
-import hudson.model.AbstractBuild
-import hudson.model.BuildListener
-import hudson.model.ParametersAction
-import hudson.model.ParametersDefinitionProperty
-import hudson.model.ParameterDefinition
-import hudson.model.Result
-import hudson.model.StringParameterDefinition
-import hudson.tasks.BatchFile
-import hudson.tasks.Builder
-import hudson.tasks.Shell
-import jenkins.model.JenkinsLocationConfiguration
-import org.junit.Assert
-import org.junit.ClassRule
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.BuildWatcher
-import org.jvnet.hudson.test.JenkinsRule
-import org.jvnet.hudson.test.TestBuilder
-
-/**
- * @author Kohsuke Kawaguchi
- */
-public class SetBuildParameterCommandTest {
- @Rule
- public JenkinsRule j = new JenkinsRule();
-
- @ClassRule
- public static BuildWatcher buildWatcher = new BuildWatcher();
-
- @Test
- public void cli() {
- JenkinsLocationConfiguration.get().url = j.URL;
-
- def p = j.createFreeStyleProject();
- p.buildersList.add(new TestBuilder() {
- @Override
- boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
- def jar = j.jenkins.servletContext.getResource("/WEB-INF/jenkins-cli.jar")
- build.workspace.child("cli.jar").copyFrom(jar);
-
- return true;
- }
- });
- List pd = [new StringParameterDefinition("a", ""), new StringParameterDefinition("b", "")];
- p.addProperty(new ParametersDefinitionProperty(pd))
- p.buildersList.add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter a b"))
- p.buildersList.add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter a x"))
- p.buildersList.add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter b y"))
-
- def r = [:];
-
- def b = j.assertBuildStatusSuccess(p.scheduleBuild2(0))
- b.getAction(ParametersAction.class).parameters.each { v -> r[v.name]=v.value }
-
- assert r.equals(["a":"x", "b":"y"]);
-
- if (Functions.isWindows()) {
- p.buildersList.add(new BatchFile("set BUILD_NUMBER=1\r\njava -jar cli.jar -remoting -noKeyAuth set-build-parameter a b"))
- } else {
- p.buildersList.add(new Shell("BUILD_NUMBER=1 java -jar cli.jar -remoting -noKeyAuth set-build-parameter a b"))
- }
- def b2 = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get());
- j.assertLogContains("#1 is not currently being built", b2)
- r = [:];
- b.getAction(ParametersAction.class).parameters.each { v -> r[v.name]=v.value }
- assert r.equals(["a":"x", "b":"y"]);
- }
-
- //TODO: determine if this should be pulled out into JenkinsRule or something
- /**
- * Create a script based builder (either Shell or BatchFile) depending on platform
- * @param script the contents of the script to run
- * @return A Builder instance of either Shell or BatchFile
- */
- private Builder createScriptBuilder(String script) {
- return Functions.isWindows() ? new BatchFile(script) : new Shell(script);
- }
-}
diff --git a/test/src/test/groovy/hudson/model/AbstractItemTest.groovy b/test/src/test/groovy/hudson/model/AbstractItemTest.groovy
deleted file mode 100644
index 8c7cbe8ef4..0000000000
--- a/test/src/test/groovy/hudson/model/AbstractItemTest.groovy
+++ /dev/null
@@ -1,42 +0,0 @@
-package hudson.model
-
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.JenkinsRule
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class AbstractItemTest {
- @Rule
- public JenkinsRule j = new JenkinsRule()
-
- /**
- * Tests the reload functionality
- */
- @Test
- void reload() {
- def jenkins = j.jenkins;
- def p = jenkins.createProject(FreeStyleProject.class,"foo");
- p.description = "Hello World";
-
- def b = j.assertBuildStatusSuccess(p.scheduleBuild2(0))
- b.description = "This is my build"
-
- // update on disk representation
- def f = p.configFile.file
- f.text = f.text.replaceAll("Hello World","Good Evening");
-
- // reload away
- p.doReload()
-
- assert p.description == "Good Evening";
-
- def b2 = p.getBuildByNumber(1)
-
- assert !b.is(b2); // should be different object
- assert b.description == b2.description // but should have the same properties
- }
-}
diff --git a/test/src/test/groovy/hudson/model/AbstractProjectTest.groovy b/test/src/test/groovy/hudson/model/AbstractProjectTest.groovy
deleted file mode 100644
index a8941e0c66..0000000000
--- a/test/src/test/groovy/hudson/model/AbstractProjectTest.groovy
+++ /dev/null
@@ -1,596 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package hudson.model;
-
-import com.gargoylesoftware.htmlunit.ElementNotFoundException
-import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
-import com.gargoylesoftware.htmlunit.HttpMethod
-import com.gargoylesoftware.htmlunit.WebRequest;
-import hudson.tasks.BatchFile;
-import hudson.tasks.BuildStepMonitor;
-import hudson.tasks.Recorder;
-import com.gargoylesoftware.htmlunit.html.HtmlPage
-import hudson.maven.MavenModuleSet;
-import hudson.security.*;
-import hudson.tasks.Shell;
-import hudson.scm.NullSCM;
-import hudson.scm.SCM
-import hudson.scm.SCMDescriptor
-import hudson.Launcher;
-import hudson.FilePath;
-import hudson.Functions;
-import hudson.Util;
-import hudson.tasks.ArtifactArchiver
-import hudson.triggers.SCMTrigger;
-import hudson.triggers.TimerTrigger
-import hudson.triggers.Trigger
-import hudson.triggers.TriggerDescriptor;
-import hudson.util.StreamTaskListener;
-import hudson.util.OneShotEvent
-import jenkins.model.Jenkins
-import org.junit.Rule
-import org.junit.Test;
-import org.jvnet.hudson.test.Issue
-import org.jvnet.hudson.test.JenkinsRule;
-import org.jvnet.hudson.test.TestExtension;
-import org.jvnet.hudson.test.recipes.PresetData;
-import org.jvnet.hudson.test.recipes.PresetData.DataSet
-import org.apache.commons.io.FileUtils;
-import org.junit.Assume;
-import org.jvnet.hudson.test.MockFolder
-import org.kohsuke.args4j.CmdLineException
-
-import static org.junit.Assert.fail
-import static org.junit.Assert.assertEquals;
-
-/**
- * @author Kohsuke Kawaguchi
- */
-public class AbstractProjectTest {
-
- @Rule public JenkinsRule j = new JenkinsRule();
-
- @Test
- public void testConfigRoundtrip() {
- def project = j.createFreeStyleProject();
- def l = j.jenkins.getLabel("foo && bar");
- project.assignedLabel = l;
- j.configRoundtrip((Item) project);
-
- assert l == project.getAssignedLabel();
- }
-
- /**
- * Tests the workspace deletion.
- */
- @Test
- public void testWipeWorkspace() {
- def project = j.createFreeStyleProject();
- project.buildersList.add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("echo hello"));
-
- def b = project.scheduleBuild2(0).get();
-
- assert b.workspace.exists(): "Workspace should exist by now";
-
- project.doDoWipeOutWorkspace();
-
- assert !b.workspace.exists(): "Workspace should be gone by now";
- }
-
- /**
- * Makes sure that the workspace deletion is protected.
- */
- @Test
- @PresetData(DataSet.NO_ANONYMOUS_READACCESS)
- public void testWipeWorkspaceProtected() {
- def project = j.createFreeStyleProject();
- project.getBuildersList().add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("echo hello"));
-
- def b = project.scheduleBuild2(0).get();
-
- assert b.getWorkspace().exists(): "Workspace should exist by now";
-
- // make sure that the action link is protected
- com.gargoylesoftware.htmlunit.WebClient wc = j.createWebClient();
- try {
- wc.getPage(new WebRequest(new URL(wc.getContextPath() + project.getUrl() + "doWipeOutWorkspace"), HttpMethod.POST));
- fail("Expected HTTP status code 403")
- } catch (FailingHttpStatusCodeException e) {
- assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getStatusCode());
- }
- }
-
- /**
- * Makes sure that the workspace deletion link is not provided
- * when the user doesn't have an access.
- */
- @Test
- @PresetData(DataSet.ANONYMOUS_READONLY)
- public void testWipeWorkspaceProtected2() {
- ((GlobalMatrixAuthorizationStrategy) j.jenkins.getAuthorizationStrategy()).add(AbstractProject.WORKSPACE,"anonymous");
-
- // make sure that the deletion is protected in the same way
- testWipeWorkspaceProtected();
-
- // there shouldn't be any "wipe out workspace" link for anonymous user
- def webClient = j.createWebClient();
- HtmlPage page = webClient.getPage(j.jenkins.getItem("test0"));
-
- page = (HtmlPage)page.getAnchorByText("Workspace").click();
- try {
- String wipeOutLabel = ResourceBundle.getBundle("hudson/model/AbstractProject/sidepanel").getString("Wipe Out Workspace");
- page.getAnchorByText(wipeOutLabel);
- fail("shouldn't find a link");
- } catch (ElementNotFoundException e) {
- // OK
- }
- }
-
- /**
- * Tests the <optionalBlock @field> round trip behavior by using {@link AbstractProject#concurrentBuild}
- */
- @Test
- public void testOptionalBlockDataBindingRoundtrip() {
- def p = j.createFreeStyleProject();
- [true,false].each { b ->
- p.concurrentBuild = b;
- j.submit(j.createWebClient().getPage(p,"configure").getFormByName("config"));
- assert b==p.isConcurrentBuild();
- }
- }
-
- /**
- * Tests round trip configuration of the blockBuildWhenUpstreamBuilding field
- */
- @Test
- @Issue("JENKINS-4423")
- public void testConfiguringBlockBuildWhenUpstreamBuildingRoundtrip() {
- def p = j.createFreeStyleProject();
- p.blockBuildWhenUpstreamBuilding = false;
-
- def form = j.createWebClient().getPage(p, "configure").getFormByName("config");
- def input = form.getInputByName("blockBuildWhenUpstreamBuilding");
- assert !input.isChecked(): "blockBuildWhenUpstreamBuilding check box is checked.";
-
- input.setChecked(true);
- j.submit(form);
- assert p.blockBuildWhenUpstreamBuilding: "blockBuildWhenUpstreamBuilding was not updated from configuration form";
-
- form = j.createWebClient().getPage(p, "configure").getFormByName("config");
- input = form.getInputByName("blockBuildWhenUpstreamBuilding");
- assert input.isChecked(): "blockBuildWhenUpstreamBuilding check box is not checked.";
- }
-
- /**
- * Unless the concurrent build option is enabled, polling and build should be mutually exclusive
- * to avoid allocating unnecessary workspaces.
- */
- @Test
- @Issue("JENKINS-4202")
- public void testPollingAndBuildExclusion() {
- final OneShotEvent sync = new OneShotEvent();
-
- final FreeStyleProject p = j.createFreeStyleProject();
- def b1 = j.buildAndAssertSuccess(p);
-
- p.scm = new NullSCM() {
- @Override
- public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener) {
- try {
- sync.block();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return true;
- }
-
- /**
- * Don't write 'this', so that subtypes can be implemented as anonymous class.
- */
- private Object writeReplace() { return new Object(); }
-
- @Override public boolean requiresWorkspaceForPolling() {
- return true;
- }
- @Override public SCMDescriptor> getDescriptor() {
- return new SCMDescriptor(null) {};
- }
- };
- Thread t = new Thread() {
- @Override public void run() {
- p.pollSCMChanges(StreamTaskListener.fromStdout());
- }
- };
- try {
- t.start();
- def f = p.scheduleBuild2(0);
-
- // add a bit of delay to make sure that the blockage is happening
- Thread.sleep(3000);
-
- // release the polling
- sync.signal();
-
- def b2 = j.assertBuildStatusSuccess(f);
-
- // they should have used the same workspace.
- assert b1.workspace == b2.workspace;
- } finally {
- t.interrupt();
- }
- }
-
- @Test
- @Issue("JENKINS-1986")
- public void testBuildSymlinks() {
- Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows());
-
- def job = j.createFreeStyleProject();
- job.buildersList.add(new Shell("echo \"Build #\$BUILD_NUMBER\"\n"));
- def build = job.scheduleBuild2(0, new Cause.UserCause()).get();
- File lastSuccessful = new File(job.rootDir, "lastSuccessful"),
- lastStable = new File(job.rootDir, "lastStable");
- // First build creates links
- assertSymlinkForBuild(lastSuccessful, 1);
- assertSymlinkForBuild(lastStable, 1);
- FreeStyleBuild build2 = job.scheduleBuild2(0, new Cause.UserCause()).get();
- // Another build updates links
- assertSymlinkForBuild(lastSuccessful, 2);
- assertSymlinkForBuild(lastStable, 2);
- // Delete latest build should update links
- build2.delete();
- assertSymlinkForBuild(lastSuccessful, 1);
- assertSymlinkForBuild(lastStable, 1);
- // Delete all builds should remove links
- build.delete();
- assert !lastSuccessful.exists(): "lastSuccessful link should be removed";
- assert !lastStable.exists(): "lastStable link should be removed";
- }
-
- private static void assertSymlinkForBuild(File file, int buildNumber)
- throws IOException, InterruptedException {
- assert file.exists(): "should exist and point to something that exists";
- assert Util.isSymlink(file): "should be symlink";
- String s = FileUtils.readFileToString(new File(file, "log"));
- assert s.contains("Build #" + buildNumber + "\n") : "link should point to build #$buildNumber, but link was: ${Util.resolveSymlink(file, TaskListener.NULL)}\nand log was:\n$s";
- }
-
- @Test
- @Issue("JENKINS-2543")
- public void testSymlinkForPostBuildFailure() {
- Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows());
-
- // Links should be updated after post-build actions when final build result is known
- def job = j.createFreeStyleProject();
- job.buildersList.add(new Shell("echo \"Build #\$BUILD_NUMBER\"\n"));
- def build = job.scheduleBuild2(0, new Cause.UserCause()).get();
- assert Result.SUCCESS == build.result;
- File lastSuccessful = new File(job.rootDir, "lastSuccessful"),
- lastStable = new File(job.rootDir, "lastStable");
- // First build creates links
- assertSymlinkForBuild(lastSuccessful, 1);
- assertSymlinkForBuild(lastStable, 1);
- // Archive artifacts that don't exist to create failure in post-build action
- job.publishersList.add(new ArtifactArchiver("*.foo", "", false, false));
- build = job.scheduleBuild2(0, new Cause.UserCause()).get();
- assert Result.FAILURE == build.getResult();
- // Links should not be updated since build failed
- assertSymlinkForBuild(lastSuccessful, 1);
- assertSymlinkForBuild(lastStable, 1);
- }
-
- /* TODO too slow, seems capable of causing testWorkspaceLock to time out:
- @Test
- @Issue("JENKINS-15156")
- public void testGetBuildAfterGC() {
- FreeStyleProject job = j.createFreeStyleProject();
- job.scheduleBuild2(0, new Cause.UserIdCause()).get();
- j.jenkins.queue.clearLeftItems();
- MemoryAssert.assertGC(new WeakReference(job.getLastBuild()));
- assert job.lastBuild != null;
- }
- */
-
- @Test
- @Issue("JENKINS-17137")
- public void testExternalBuildDirectorySymlinks() {
- Assume.assumeFalse(Functions.isWindows()); // symlinks may not be available
- def form = j.createWebClient().goTo("configure").getFormByName("config");
- def builds = j.createTmpDir();
- form.getInputByName("_.rawBuildsDir").valueAttribute = builds.toString() + "/\${ITEM_FULL_NAME}";
- j.submit(form);
- assert builds.toString() + "/\${ITEM_FULL_NAME}" == j.jenkins.getRawBuildsDir();
- def p = j.jenkins.createProject(MockFolder.class, "d").createProject(FreeStyleProject.class, "p");
- def b1 = p.scheduleBuild2(0).get();
- def link = new File(p.rootDir, "lastStable");
- assert link.exists();
- assert b1.rootDir.absolutePath == resolveAll(link).absolutePath;
- def b2 = p.scheduleBuild2(0).get();
- assert link.exists();
- assert b2.rootDir.absolutePath == resolveAll(link).absolutePath;
- b2.delete();
- assert link.exists();
- assert b1.rootDir.absolutePath == resolveAll(link).absolutePath;
- b1.delete();
- assert !link.exists();
- }
-
- private File resolveAll(File link) throws InterruptedException, IOException {
- while (true) {
- File f = Util.resolveSymlinkToFile(link);
- if (f==null) return link;
- link = f;
- }
- }
-
- @Test
- @Issue("JENKINS-17138")
- public void testExternalBuildDirectoryRenameDelete() {
- def form = j.createWebClient().goTo("configure").getFormByName("config");
- def builds = j.createTmpDir();
- form.getInputByName("_.rawBuildsDir").setValueAttribute(builds.toString() + "/\${ITEM_FULL_NAME}");
- j.submit(form);
- assert builds.toString() + "/\${ITEM_FULL_NAME}" == j.jenkins.rawBuildsDir;
- def p = j.jenkins.createProject(MockFolder.class, "d").createProject(FreeStyleProject.class, "prj");
- def b = p.scheduleBuild2(0).get();
- def oldBuildDir = new File(builds, "d/prj");
- assert new File(oldBuildDir, b.id) == b.rootDir;
- assert b.getRootDir().isDirectory();
- p.renameTo("proj");
- def newBuildDir = new File(builds, "d/proj");
- assert new File(newBuildDir, b.id) == b.rootDir;
- assert b.rootDir.isDirectory();
- p.delete();
- assert !b.rootDir.isDirectory();
- }
-
- @Test
- @Issue("JENKINS-18678")
- public void testRenameJobLostBuilds() throws Exception {
- def p = j.createFreeStyleProject("initial");
- j.assertBuildStatusSuccess(p.scheduleBuild2(0));
- assertEquals(1, p.getBuilds().size());
- p.renameTo("edited");
- p._getRuns().purgeCache();
- assertEquals(1, p.getBuilds().size());
- def d = j.jenkins.createProject(MockFolder.class, "d");
- Items.move(p, d);
- assertEquals(p, j.jenkins.getItemByFullName("d/edited"));
- p._getRuns().purgeCache();
- assertEquals(1, p.getBuilds().size());
- d.renameTo("d2");
- p = j.jenkins.getItemByFullName("d2/edited");
- p._getRuns().purgeCache();
- assertEquals(1, p.getBuilds().size());
- }
-
- @Test
- @Issue("JENKINS-17575")
- public void testDeleteRedirect() {
- j.createFreeStyleProject("j1");
- assert "" == deleteRedirectTarget("job/j1");
- j.createFreeStyleProject("j2");
- Jenkins.getInstance().addView(new AllView("v1"));
- assert "view/v1/" == deleteRedirectTarget("view/v1/job/j2");
- MockFolder d = Jenkins.getInstance().createProject(MockFolder.class, "d");
- d.addView(new AllView("v2"));
- ["j3","j4","j5"].each { n -> d.createProject(FreeStyleProject.class, n) }
- assert "job/d/" == deleteRedirectTarget("job/d/job/j3");
- assert "job/d/view/v2/" == deleteRedirectTarget("job/d/view/v2/job/j4");
- assert "view/v1/job/d/" == deleteRedirectTarget("view/v1/job/d/job/j5");
- assert "view/v1/" == deleteRedirectTarget("view/v1/job/d"); // JENKINS-23375
- }
-
- private String deleteRedirectTarget(String job) {
- def wc = j.createWebClient();
- String base = wc.getContextPath();
- String loc = wc.getPage(wc.addCrumb(new WebRequest(new URL(base + job + "/doDelete"), HttpMethod.POST))).getUrl().toString();
- assert loc.startsWith(base): loc;
- return loc.substring(base.length());
- }
-
- @Test
- @Issue("JENKINS-18407")
- public void testQueueSuccessBehavior() {
- // prevent any builds to test the behaviour
- j.jenkins.numExecutors = 0;
-
- def p = j.createFreeStyleProject()
- def f = p.scheduleBuild2(0)
- assert f!=null;
- def g = p.scheduleBuild2(0)
- assert f==g;
-
- p.makeDisabled(true)
- assert p.scheduleBuild2(0)==null
- }
-
- /**
- * Do the same as {@link #testQueueSuccessBehavior()} but over HTTP
- */
- @Test
- @Issue("JENKINS-18407")
- public void testQueueSuccessBehaviorOverHTTP() {
- // prevent any builds to test the behaviour
- j.jenkins.numExecutors = 0;
-
- def p = j.createFreeStyleProject()
- def wc = j.createWebClient();
-
- def rsp = wc.getPage("${j.getURL()}${p.url}build").webResponse
- assert rsp.statusCode==201;
- assert rsp.getResponseHeaderValue("Location")!=null;
-
- def rsp2 = wc.getPage("${j.getURL()}${p.url}build").webResponse
- assert rsp2.statusCode==201;
- assert rsp.getResponseHeaderValue("Location")==rsp2.getResponseHeaderValue("Location")
-
- p.makeDisabled(true)
-
- try {
- wc.getPage("${j.getURL()}${p.url}build")
- fail();
- } catch (FailingHttpStatusCodeException e) {
- // request should fail
- }
- }
-
- /**
- * We used to store {@link AbstractProject#triggers} as {@link Vector}, so make sure
- * we can still read back the configuration from that.
- */
- @Test
- public void testVectorTriggers() {
- AbstractProject p = j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml"))
- assert p.triggers().size()==1
- def t = p.triggers()[0]
- assert t.class==SCMTrigger.class;
- assert t.spec=="*/10 * * * *"
- }
-
- @Test
- @Issue("JENKINS-18813")
- public void testRemoveTrigger() {
- AbstractProject p = j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml"))
-
- TriggerDescriptor SCM_TRIGGER_DESCRIPTOR = j.jenkins.getDescriptorOrDie(SCMTrigger.class)
- p.removeTrigger(SCM_TRIGGER_DESCRIPTOR);
- assert p.triggers().size()==0
- }
-
- @Test
- @Issue("JENKINS-18813")
- public void testAddTriggerSameType() {
- AbstractProject p = j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml"))
-
- def newTrigger = new SCMTrigger("H/5 * * * *")
- p.addTrigger(newTrigger);
-
- assert p.triggers().size()==1
- def t = p.triggers()[0]
- assert t.class==SCMTrigger.class;
- assert t.spec=="H/5 * * * *"
- }
-
- @Test
- @Issue("JENKINS-18813")
- public void testAddTriggerDifferentType() {
- AbstractProject p = j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml"))
-
- def newTrigger = new TimerTrigger("20 * * * *")
- p.addTrigger(newTrigger);
-
- assert p.triggers().size()==2
- def t = p.triggers()[1]
- assert t == newTrigger
- }
-
- /**
- * Trying to POST to config.xml by a different job type should fail.
- */
- @Test
- public void testConfigDotXmlSubmissionToDifferentType() {
- j.jenkins.pluginManager.installDetachedPlugin("javadoc")
- j.jenkins.pluginManager.installDetachedPlugin("junit")
- j.jenkins.pluginManager.installDetachedPlugin("display-url-api")
- j.jenkins.pluginManager.installDetachedPlugin("mailer")
- j.jenkins.pluginManager.installDetachedPlugin("maven-plugin")
-
- j.jenkins.crumbIssuer = null
- def p = j.createFreeStyleProject()
-
- HttpURLConnection con = postConfigDotXml(p, " ")
-
- // this should fail with a type mismatch error
- // the error message should report both what was submitted and what was expected
- assert con.responseCode == 500
- def msg = con.errorStream.text
- println msg
- assert msg.contains(FreeStyleProject.class.name)
- assert msg.contains(MavenModuleSet.class.name)
-
- // control. this should work
- con = postConfigDotXml(p, " ")
- assert con.responseCode == 200
- }
-
- private HttpURLConnection postConfigDotXml(FreeStyleProject p, String xml) {
- HttpURLConnection con = new URL(j.getURL(), "job/${p.name}/config.xml").openConnection()
- con.requestMethod = "POST"
- con.setRequestProperty("Content-Type", "application/xml")
- con.doOutput = true
- con.outputStream.withStream { s ->
- s.write(xml.bytes)
- }
- return con
- }
-
- @Test
- @Issue("JENKINS-27549")
- public void testLoadingWithNPEOnTriggerStart() {
- AbstractProject project = j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/npeTrigger.xml"))
-
- assert project.triggers().size() == 1
- }
-
- @Test
- @Issue("JENKINS-30742")
- public void testResolveForCLI() {
- try {
- AbstractProject not_found = AbstractProject.resolveForCLI("never_created");
- fail("Exception should occur before!");
- } catch (CmdLineException e) {
- assert e.getMessage().contentEquals("No such job \u2018never_created\u2019 exists.");
- }
-
- AbstractProject project = j.jenkins.createProject(FreeStyleProject.class, "never_created");
- try {
- AbstractProject not_found = AbstractProject.resolveForCLI("never_created1");
- fail("Exception should occur before!");
- } catch (CmdLineException e) {
- assert e.getMessage().contentEquals("No such job \u2018never_created1\u2019 exists. Perhaps you meant \u2018never_created\u2019?")
- }
-
- }
-
- static class MockBuildTriggerThrowsNPEOnStart extends Trigger- {
- @Override
- public void start(hudson.model.Item project, boolean newInstance) { throw new NullPointerException(); }
-
- @Override
- public TriggerDescriptor getDescriptor() {
- return DESCRIPTOR;
- }
-
- public static final TriggerDescriptor DESCRIPTOR = new DescriptorImpl()
-
- @TestExtension("testLoadingWithNPEOnTriggerStart")
- static class DescriptorImpl extends TriggerDescriptor {
-
- public boolean isApplicable(hudson.model.Item item) {
- return false;
- }
- }
- }
-}
diff --git a/test/src/test/groovy/hudson/model/EnvironmentContributorTest.groovy b/test/src/test/groovy/hudson/model/EnvironmentContributorTest.groovy
deleted file mode 100644
index fab4c865f6..0000000000
--- a/test/src/test/groovy/hudson/model/EnvironmentContributorTest.groovy
+++ /dev/null
@@ -1,41 +0,0 @@
-package hudson.model
-
-import hudson.EnvVars
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.CaptureEnvironmentBuilder
-import org.jvnet.hudson.test.JenkinsRule
-import org.jvnet.hudson.test.TestExtension
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class EnvironmentContributorTest {
- @Rule
- public JenkinsRule j = new JenkinsRule()
-
- /**
- * Makes sure that the project-scoped environment variables are getting consulted.
- */
- @Test
- public void testProjectScoped() {
- def p = j.createFreeStyleProject()
- def c = new CaptureEnvironmentBuilder()
- p.buildersList.add(c)
- p.description = "Issac Newton";
- j.assertBuildStatusSuccess(p.scheduleBuild2(0))
-
- assert c.envVars["ABC"]=="Issac Newton";
- assert c.envVars["NODE_NAME"]=="master";
- }
-
- @TestExtension("testProjectScoped")
- public static class JobScopedInjection extends EnvironmentContributor {
- @Override
- void buildEnvironmentFor(Job j, EnvVars envs, TaskListener listener) {
- envs.put("ABC",j.description)
- }
- }
-}
diff --git a/test/src/test/groovy/hudson/model/SlaveTest.groovy b/test/src/test/groovy/hudson/model/SlaveTest.groovy
deleted file mode 100644
index 35299a7e4f..0000000000
--- a/test/src/test/groovy/hudson/model/SlaveTest.groovy
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2004-2009, Sun Microsystems, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package hudson.model
-
-import static hudson.util.FormValidation.Kind.ERROR
-import static hudson.util.FormValidation.Kind.WARNING
-import static org.junit.Assert.assertNotNull
-import static org.junit.Assert.assertEquals
-
-import hudson.security.csrf.CrumbIssuer
-import hudson.slaves.DumbSlave
-import hudson.slaves.JNLPLauncher
-import hudson.util.FormValidation
-import org.junit.Rule
-import org.junit.Test
-import org.apache.commons.io.IOUtils
-import org.jvnet.hudson.test.GroovyJenkinsRule
-
-/**
- * @author Kohsuke Kawaguchi
- */
-class SlaveTest {
-
- @Rule
- public GroovyJenkinsRule j = new GroovyJenkinsRule()
-
- /**
- * Makes sure that a form validation method gets inherited.
- */
- @Test
- void formValidation() {
- j.executeOnServer {
- assertNotNull(j.jenkins.getDescriptor(DumbSlave).getCheckUrl("remoteFS"))
- }
- }
-
- /**
- * Programmatic config.xml submission.
- */
- @Test
- void slaveConfigDotXml() {
- DumbSlave s = j.createSlave()
- def wc = j.createWebClient()
- def p = wc.goTo("computer/${s.name}/config.xml", "application/xml")
- def xml = p.webResponse.contentAsString
- new XmlSlurper().parseText(xml) // verify that it is XML
-
- // make sure it survives the roundtrip
- post("computer/${s.name}/config.xml",xml);
-
- assertNotNull(j.jenkins.getNode(s.name))
-
- xml = IOUtils.toString(getClass().getResource("SlaveTest/slave.xml").openStream());
- xml = xml.replace("NAME",s.name)
- post("computer/${s.name}/config.xml",xml);
-
- s = j.jenkins.getNode(s.name)
- assertNotNull(s)
- assertEquals("some text",s.nodeDescription)
- assertEquals(JNLPLauncher.class,s.launcher.class)
- }
-
- def post(url,String xml) {
- HttpURLConnection con = new URL(j.getURL(),url).openConnection();
- con.requestMethod = "POST"
- con.setRequestProperty("Content-Type","application/xml;charset=UTF-8")
- con.setRequestProperty(CrumbIssuer.DEFAULT_CRUMB_NAME,"test")
- con.doOutput = true;
- con.outputStream.write(xml.getBytes("UTF-8"))
- con.outputStream.close();
- IOUtils.copy(con.inputStream,System.out)
- }
-
- @Test
- void remoteFsCheck() {
- def d = j.jenkins.getDescriptorByType(DumbSlave.DescriptorImpl.class)
- assert d.doCheckRemoteFS("c:\\")==FormValidation.ok();
- assert d.doCheckRemoteFS("/tmp")==FormValidation.ok();
- assert d.doCheckRemoteFS("relative/path").kind==WARNING;
- assert d.doCheckRemoteFS("/net/foo/bar/zot").kind==WARNING;
- assert d.doCheckRemoteFS("\\\\machine\\folder\\foo").kind==WARNING;
- }
-}
diff --git a/test/src/test/groovy/hudson/security/TokenBasedRememberMeServices2Test.groovy b/test/src/test/groovy/hudson/security/TokenBasedRememberMeServices2Test.groovy
deleted file mode 100644
index d0c6ae2880..0000000000
--- a/test/src/test/groovy/hudson/security/TokenBasedRememberMeServices2Test.groovy
+++ /dev/null
@@ -1,129 +0,0 @@
-package hudson.security
-
-import jenkins.model.Jenkins
-import org.acegisecurity.AuthenticationException
-import org.acegisecurity.BadCredentialsException
-import org.acegisecurity.GrantedAuthority
-import org.acegisecurity.GrantedAuthorityImpl
-import org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices
-import org.acegisecurity.userdetails.User
-import org.acegisecurity.userdetails.UserDetails
-import org.acegisecurity.userdetails.UsernameNotFoundException
-import org.junit.Before
-import com.gargoylesoftware.htmlunit.util.Cookie
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.JenkinsRule
-import org.jvnet.hudson.test.LoggerRule
-import org.springframework.dao.DataAccessException
-
-import java.util.logging.Handler
-import java.util.logging.LogRecord
-import java.util.logging.Logger
-
-import static java.util.logging.Level.FINEST
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class TokenBasedRememberMeServices2Test {
- @Rule
- public JenkinsRule j = new JenkinsRule();
- @Rule
- public LoggerRule logging = new LoggerRule()
-
- private static boolean failureInduced;
-
- @Before
- public void resetFailureInduced() {failureInduced = false}
-
- @Test
- public void rememberMeAutoLoginFailure() {
- j.jenkins.securityRealm = new InvalidUserWhenLoggingBackInRealm()
-
- def wc = j.createWebClient()
- wc.login("alice","alice",true)
-
- // we should see a remember me cookie
- def c = getRememberMeCookie(wc)
- assert c!=null
-
- // start a new session and attempt to access Jenkins,
- // which should cause autoLogin failures
- wc = j.createWebClient()
- wc.cookieManager.addCookie(c);
-
- // even if SecurityRealm chokes, it shouldn't kill the page
- logging.capture(1000).record(TokenBasedRememberMeServices.class, FINEST)
- wc.goTo("")
-
- // make sure that the server recorded this failure
- assert failureInduced
- assert logging.messages.find { it.contains("contained username 'alice' but was not found")}!=null
- // and the problematic cookie should have been removed
- assert getRememberMeCookie(wc)==null
- }
-
- private Cookie getRememberMeCookie(JenkinsRule.WebClient wc) {
- wc.cookieManager.getCookie(TokenBasedRememberMeServices2.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY)
- }
-
- private static class InvalidUserWhenLoggingBackInRealm extends AbstractPasswordBasedSecurityRealm {
- @Override
- protected UserDetails authenticate(String username, String password) throws AuthenticationException {
- if (username==password)
- return new User(username,password,true,[new GrantedAuthorityImpl("myteam")] as GrantedAuthority[])
- throw new BadCredentialsException(username);
- }
-
- @Override
- GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException {
- throw new UnsupportedOperationException()
- }
-
- @Override
- UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
- failureInduced = true
- throw new UsernameNotFoundException("intentionally not working");
- }
- }
-
-
- @Test
- public void basicFlow() {
- j.jenkins.securityRealm = new StupidRealm()
-
- def wc = j.createWebClient()
- wc.login("bob","bob",true)
-
- // we should see a remember me cookie
- def c = getRememberMeCookie(wc)
- assert c!=null
-
- // start a new session and attempt to access Jenkins,
- wc = j.createWebClient()
- wc.cookieManager.addCookie(c);
-
- // this will trigger remember me
- wc.goTo("")
-
- // make sure that our security realm failed to report the info correctly
- assert failureInduced
- // but we should have logged in
- wc.executeOnServer {
- def a = Jenkins.getAuthentication()
- assert a.name=="bob"
- assert a.authorities*.authority.join(":")=="authenticated:myteam"
- }
- }
-
- private static class StupidRealm extends InvalidUserWhenLoggingBackInRealm {
- @Override
- UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
- failureInduced = true
- throw new UserMayOrMayNotExistException("I cannot tell");
- }
- }
-}
diff --git a/test/src/test/groovy/hudson/util/TextFileTest.groovy b/test/src/test/groovy/hudson/util/TextFileTest.groovy
deleted file mode 100644
index 0299132fc8..0000000000
--- a/test/src/test/groovy/hudson/util/TextFileTest.groovy
+++ /dev/null
@@ -1,102 +0,0 @@
-package hudson.util
-
-import org.junit.After
-import org.junit.Test
-
-import java.nio.charset.Charset
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class TextFileTest {
- List
files = [];
-
- @After
- void tearDown() {
- files*.delete()
- }
-
- @Test
- public void head() {
- def f = newFile()
- f.text = getClass().getResource("ascii.txt").text
-
- def t = new TextFile(f)
- def first35 = "Lorem ipsum dolor sit amet, consect"
- assert t.head(35).equals(first35)
- assert first35.length()==35
- }
-
- @Test
- public void shortHead() {
- def f = newFile()
- f.text = "hello"
-
- def t = new TextFile(f)
- assert t.head(35).equals("hello")
- }
-
- @Test
- public void tail() {
- def f = newFile()
- f.text = getClass().getResource("ascii.txt").text
-
- def t = new TextFile(f)
- def tailStr = "la, vitae interdum quam rutrum id." + System.lineSeparator()
- assert t.fastTail(tailStr.length()).equals(tailStr)
- assert tailStr.length()==(34 + System.lineSeparator().length())
- }
-
- @Test
- public void shortTail() {
- def f = newFile()
- f.text = "hello"
-
- def t = new TextFile(f)
- assert t.fastTail(35).equals("hello")
- }
-
- /**
- * Shift JIS is a multi-byte character encoding.
- *
- * In it, 0x82 0x83 is \u30e2, and 0x83 0x82 is \uFF43.
- * So if aren't careful, we'll parse the text incorrectly.
- */
- @Test
- public void tailShiftJIS() {
- def f = newFile()
-
- def t = new TextFile(f)
-
- f.withOutputStream { o ->
- (1..80).each {
- (1..40).each {
- o.write(0x83)
- o.write(0x82)
- }
- o.write(0x0A);
- }
- }
-
- def tail = t.fastTail(35, Charset.forName("Shift_JIS"))
- assert tail.equals("\u30e2"*34+"\n")
- assert tail.length()==35
-
- // add one more byte to force fastTail to read from one byte ahead
- // between this and the previous case, it should start parsing text incorrectly, until it hits NL
- // where it comes back in sync
- f.append([0x0A] as byte[])
-
- tail = t.fastTail(35, Charset.forName("Shift_JIS"))
- assert tail.equals("\u30e2"*33+"\n\n")
- assert tail.length()==35
- }
-
- def newFile() {
- def f = File.createTempFile("foo", "txt")
- files.add(f)
- return f
- }
-}
diff --git a/test/src/test/groovy/hudson/widgets/HistoryWidgetTest.groovy b/test/src/test/groovy/hudson/widgets/HistoryWidgetTest.groovy
deleted file mode 100644
index 9d32acfa6b..0000000000
--- a/test/src/test/groovy/hudson/widgets/HistoryWidgetTest.groovy
+++ /dev/null
@@ -1,28 +0,0 @@
-package hudson.widgets
-
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.Issue
-import org.jvnet.hudson.test.JenkinsRule
-
-/**
- * @author Kohsuke Kawaguchi
- */
-class HistoryWidgetTest {
-
- @Rule
- public JenkinsRule j = new JenkinsRule()
-
- @Test
- @Issue("JENKINS-15499")
- void moreLink() {
- def p = j.createFreeStyleProject()
- for (x in 1..3) {
- j.assertBuildStatusSuccess(p.scheduleBuild2(0))
- }
-
- def wc = j.createWebClient()
- wc.javaScriptEnabled = false
- wc.goTo("job/${p.name}/buildHistory/all")
- }
-}
diff --git a/test/src/test/groovy/jenkins/bugs/Jenkins19124Test.groovy b/test/src/test/groovy/jenkins/bugs/Jenkins19124Test.groovy
deleted file mode 100644
index 352b3144f0..0000000000
--- a/test/src/test/groovy/jenkins/bugs/Jenkins19124Test.groovy
+++ /dev/null
@@ -1,89 +0,0 @@
-package jenkins.bugs
-
-import com.gargoylesoftware.htmlunit.WebClientUtil
-import hudson.Launcher
-import hudson.model.AbstractBuild
-import hudson.model.AbstractProject
-import hudson.model.BuildListener
-import hudson.tasks.BuildStepDescriptor
-import hudson.tasks.Builder
-import hudson.util.FormValidation
-import hudson.util.ListBoxModel
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.Issue
-import org.jvnet.hudson.test.JenkinsRule
-import org.jvnet.hudson.test.TestExtension
-import org.kohsuke.stapler.QueryParameter
-
-import javax.inject.Inject
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class Jenkins19124Test {
- @Rule
- public JenkinsRule j = new JenkinsRule();
-
- @Inject
- DescriptorImpl d;
-
- @Issue("JENKINS-19124")
- @Test
- public void interrelatedFormValidation() {
- j.jenkins.injector.injectMembers(this);
-
- def p = j.createFreeStyleProject();
- p.buildersList.add(new Foo());
-
- def wc = j.createWebClient();
- def c = wc.getPage(p, "configure");
- c.getElementByName("_.alpha").valueAttribute = "hello";
- WebClientUtil.waitForJSExec(wc);
- assert d.alpha=="hello";
- assert d.bravo=="2";
-
- c.getElementByName("_.bravo").setSelectedAttribute("1",true);
- WebClientUtil.waitForJSExec(wc);
- assert d.alpha=="hello";
- assert d.bravo=="1";
- }
-
- public static class Foo extends Builder {
- String getAlpha() { return "alpha"; }
- String getBravo() { return "2"; }
-
- @Override
- boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
- return true;
- }
-
- private Object writeReplace() { return new Object(); }
- }
-
- @TestExtension
- public static class DescriptorImpl extends BuildStepDescriptor {
- String alpha,bravo;
-
- public DescriptorImpl() {
- super(Foo.class)
- }
-
- FormValidation doCheckAlpha(@QueryParameter String value, @QueryParameter String bravo) {
- this.alpha = value;
- this.bravo = bravo;
- return FormValidation.ok();
- }
-
- ListBoxModel doFillBravoItems() {
- return new ListBoxModel().add("1").add("2").add("3");
- }
-
- @Override
- boolean isApplicable(Class extends AbstractProject> jobType) {
- return true;
- }
- }
-}
diff --git a/test/src/test/groovy/jenkins/model/PeepholePermalinkTest.groovy b/test/src/test/groovy/jenkins/model/PeepholePermalinkTest.groovy
deleted file mode 100644
index a500677a9f..0000000000
--- a/test/src/test/groovy/jenkins/model/PeepholePermalinkTest.groovy
+++ /dev/null
@@ -1,100 +0,0 @@
-package jenkins.model
-
-import static org.junit.Assert.assertTrue
-
-import org.junit.Assume;
-
-import hudson.Functions
-import hudson.Util
-import hudson.model.Run
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.FailureBuilder
-import org.jvnet.hudson.test.Issue
-import org.jvnet.hudson.test.JenkinsRule
-
-/**
- * @author Kohsuke Kawaguchi
- */
-class PeepholePermalinkTest {
-
- @Rule
- public JenkinsRule j = new JenkinsRule()
-
- /**
- * Basic operation of the permalink generation.
- */
- @Test
- void basics() {
- Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows());
-
- def p = j.createFreeStyleProject()
- def b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0))
-
- def lsb = new File(p.buildDir, "lastSuccessfulBuild")
- def lfb = new File(p.buildDir, "lastFailedBuild")
-
- assertLink(lsb,b1)
-
- // now another build that fails
- p.buildersList.add(new FailureBuilder())
- def b2 = p.scheduleBuild2(0).get()
-
- assertLink(lsb,b1)
- assertLink(lfb,b2)
-
- // one more build and this time it succeeds
- p.buildersList.clear()
- def b3 = j.assertBuildStatusSuccess(p.scheduleBuild2(0))
-
- assertLink(lsb,b3)
- assertLink(lfb,b2)
-
- // delete b3 and symlinks should update properly
- b3.delete()
- assertLink(lsb,b1)
- assertLink(lfb,b2)
-
- b1.delete()
- assertLink(lsb,null)
- assertLink(lfb,b2)
-
- b2.delete()
- assertLink(lsb,null)
- assertLink(lfb,null)
- }
-
- def assertLink(File symlink, Run build) {
- assert Util.resolveSymlink(symlink)==(build==null ? "-1" : build.number as String);
- }
-
- /**
- * job/JOBNAME/lastStable and job/JOBNAME/lastSuccessful symlinks that we used to generate should still work
- */
- @Test
- void legacyCompatibility() {
- Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows());
-
- def p = j.createFreeStyleProject()
- def b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0))
-
- ["lastStable","lastSuccessful"].each { n ->
- // test if they both point to b1
- assert new File(p.rootDir,"$n/build.xml").length() == new File(b1.rootDir,"build.xml").length()
- }
- }
-
- @Test
- @Issue("JENKINS-19034")
- void rebuildBuildNumberPermalinks() {
- def p = j.createFreeStyleProject()
- def b = j.assertBuildStatusSuccess(p.scheduleBuild2(0))
- File f = new File(p.getBuildDir(), "1")
- // assertTrue(Util.isSymlink(f))
- f.delete()
- PeepholePermalink link = p.getPermalinks().find({l -> l instanceof PeepholePermalink})
- println(link)
- link.updateCache(p, b)
- assertTrue("build symlink hasn't been restored", f.exists())
- }
-}
diff --git a/test/src/test/groovy/lib/form/ApplyButtonTest.groovy b/test/src/test/groovy/lib/form/ApplyButtonTest.groovy
deleted file mode 100644
index ceae5ed940..0000000000
--- a/test/src/test/groovy/lib/form/ApplyButtonTest.groovy
+++ /dev/null
@@ -1,37 +0,0 @@
-package lib.form
-
-import hudson.markup.RawHtmlMarkupFormatter
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.Issue
-import org.jvnet.hudson.test.JenkinsRule
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class ApplyButtonTest {
- @Rule public JenkinsRule j = new JenkinsRule();
-
- /**
- * Editing code mirror should still gets reflected when you click apply.
- */
- @Test @Issue("JENKINS-18436")
- public void editDescription() {
- j.jenkins.markupFormatter = RawHtmlMarkupFormatter.INSTANCE // need something using CodeMirror
- def p = j.createFreeStyleProject()
- def b = j.assertBuildStatusSuccess(p.scheduleBuild2(0))
-
- def config = j.createWebClient().getPage(b, "configure")
- def form = config.getFormByName("config")
- // HtmlUnit doesn't have JSON, so we need to emulate one
- config.executeJavaScript(getClass().getResource("JSON.js").text)
- // it's hard to emulate the keytyping, so we just set the value into codemirror and test if this gets
- // reflected back into TEXTAREA
- config.executeJavaScript("document.getElementsByTagName('TEXTAREA')[0].codemirrorObject.setLine(0,'foobar')")
- j.getButtonByCaption(form,"Apply").click()
-
- assert "foobar"==b.description
- }
-}
diff --git a/test/src/test/groovy/lib/form/TextAreaTest.groovy b/test/src/test/groovy/lib/form/TextAreaTest.groovy
deleted file mode 100644
index f5edc5cac3..0000000000
--- a/test/src/test/groovy/lib/form/TextAreaTest.groovy
+++ /dev/null
@@ -1,128 +0,0 @@
-package lib.form
-
-import hudson.model.AbstractProject
-import hudson.tasks.BuildStepDescriptor
-import hudson.tasks.Builder
-import hudson.util.FormValidation
-import org.junit.Rule
-import org.junit.Test
-import org.jvnet.hudson.test.Issue
-import org.jvnet.hudson.test.JenkinsRule
-import org.jvnet.hudson.test.TestExtension
-import org.kohsuke.stapler.DataBoundConstructor
-import org.kohsuke.stapler.QueryParameter
-
-import javax.inject.Inject
-
-import static org.junit.Assert.*
-
-/**
- *
- *
- * @author Kohsuke Kawaguchi
- */
-class TextAreaTest {
- @Rule
- public JenkinsRule j = new JenkinsRule();
-
- @Inject
- TestBuilder.DescriptorImpl d;
-
- @Test @Issue("JENKINS-19457")
- public void validation() {
- j.jenkins.injector.injectMembers(this)
- def p = j.createFreeStyleProject()
- p.buildersList.add(new TestBuilder())
- j.configRoundtrip(p)
- assert d.text1=="This is text1"
- assert d.text2=="Received This is text1"
- }
-
- public static class TestBuilder extends Builder {
- @DataBoundConstructor
- TestBuilder() {
- }
-
- public String getText1() { return "This is text1" }
- public String getText2() { return "This is text2" }
-
- @TestExtension
- public static class DescriptorImpl extends BuildStepDescriptor {
- def text1,text2;
-
- @Override
- boolean isApplicable(Class extends AbstractProject> jobType) {
- return true;
- }
-
- FormValidation doCheckText1(@QueryParameter String value) {
- this.text1 = value;
- return FormValidation.ok();
- }
-
- FormValidation doCheckText2(@QueryParameter String text1) {
- this.text2 = "Received "+text1;
- return FormValidation.ok();
- }
- }
-
- }
-
- @Issue("JENKINS-27505")
- @Test
- public void testText() {
- T1:{
- def TEXT_TO_TEST = "some\nvalue\n";
- def p = j.createFreeStyleProject();
- def target = new TextareaTestBuilder(TEXT_TO_TEST);
- p.buildersList.add(target);
- j.configRoundtrip(p);
- j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class));
- }
-
- // test for a textarea beginning with a empty line.
- T2:{
- def TEXT_TO_TEST = "\nbegin\n\nwith\nempty\nline\n\n";
- def p = j.createFreeStyleProject();
- def target = new TextareaTestBuilder(TEXT_TO_TEST);
- p.buildersList.add(target);
- j.configRoundtrip(p);
- j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class));
- }
-
- // test for a textarea beginning with two empty lines.
- T3:{
- def TEXT_TO_TEST = "\n\nbegin\n\nwith\ntwo\nempty\nline\n\n";
- def p = j.createFreeStyleProject();
- def target = new TextareaTestBuilder(TEXT_TO_TEST);
- p.buildersList.add(target);
- j.configRoundtrip(p);
- j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class));
- }
- }
-
- public static class TextareaTestBuilder extends Builder {
- private text;
-
- @DataBoundConstructor
- TextareaTestBuilder(String text) {
- this.text = text;
- }
-
- public String getText() { return text; }
-
- @TestExtension
- public static class DescriptorImpl extends BuildStepDescriptor {
- @Override
- boolean isApplicable(Class extends AbstractProject> jobType) {
- return true;
- }
-
- @Override
- String getDisplayName() {
- return this.class.name;
- }
- }
-
- }
-}
diff --git a/test/src/test/groovy/org/jvnet/hudson/test/SleepBuilderTest.groovy b/test/src/test/groovy/org/jvnet/hudson/test/SleepBuilderTest.groovy
deleted file mode 100644
index a50e3e8fea..0000000000
--- a/test/src/test/groovy/org/jvnet/hudson/test/SleepBuilderTest.groovy
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.jvnet.hudson.test
-
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * @author schristou@cloudbees.com
- * Date: 9/17/13
- * Time: 11:43 AM
- * To change this template use File | Settings | File Templates.
- */
-class SleepBuilderTest {
- @Rule public JenkinsRule j = new JenkinsRule();
- @Test
- void testPerform() {
- def project = j.createFreeStyleProject();
- def builder = new SleepBuilder(30)
- project.buildersList.add(builder);
- j.configRoundtrip(project);
- j.assertEqualDataBoundBeans(project.buildersList.get(SleepBuilder.class), builder);
- }
-}
diff --git a/test/src/test/java/hudson/PluginManagerUtil.java b/test/src/test/java/hudson/PluginManagerUtil.java
index 2263622b4c..423213b7d6 100644
--- a/test/src/test/java/hudson/PluginManagerUtil.java
+++ b/test/src/test/java/hudson/PluginManagerUtil.java
@@ -26,6 +26,8 @@ package hudson;
import jenkins.RestartRequiredException;
import jenkins.model.Jenkins;
import org.apache.commons.io.FileUtils;
+import org.junit.runner.Description;
+import org.jvnet.hudson.test.RestartableJenkinsRule;
import org.jvnet.hudson.test.JenkinsRule;
import java.io.File;
@@ -47,6 +49,15 @@ public class PluginManagerUtil {
};
}
+ public static RestartableJenkinsRule newRestartableJenkinsRule() {
+ return new RestartableJenkinsRule() {
+ @Override
+ public JenkinsRule createJenkinsRule(Description description) {
+ return newJenkinsRule();
+ }
+ };
+ }
+
public static void dynamicLoad(String plugin, Jenkins jenkins) throws IOException, InterruptedException, RestartRequiredException {
dynamicLoad(plugin, jenkins, false);
}
diff --git a/test/src/test/java/hudson/RelativePathTest.java b/test/src/test/java/hudson/RelativePathTest.java
new file mode 100644
index 0000000000..494a37f9e9
--- /dev/null
+++ b/test/src/test/java/hudson/RelativePathTest.java
@@ -0,0 +1,58 @@
+package hudson;
+
+import hudson.model.AbstractDescribableImpl;
+import hudson.model.Describable;
+import hudson.model.Descriptor;
+import hudson.util.ListBoxModel;
+import org.jvnet.hudson.test.HudsonTestCase;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.TestExtension;
+import org.kohsuke.stapler.QueryParameter;
+
+public class RelativePathTest extends HudsonTestCase implements Describable {
+
+ @Issue("JENKINS-18776")
+ public void testRelativePath() throws Exception {
+ // I was having trouble causing annotation processing on test stubs
+// jenkins.getDescriptorOrDie(RelativePathTest.class);
+// jenkins.getDescriptorOrDie(Model.class);
+
+ createWebClient().goTo("/self/");
+ assertTrue(((Model.DescriptorImpl) jenkins.getDescriptorOrDie(Model.class)).touched);
+ }
+
+ @Override // TODO this is horrible, should change the property here and in @QueryParameter and in RelativePathTest/index.groovy to, say, personName
+ public String getName() {
+ return "Alice";
+ }
+
+ public Model getModel() {
+ return new Model();
+ }
+
+ @Override // TODO would suffice to extend AbstractDescribableImpl
+ public DescriptorImpl getDescriptor() {
+ return (DescriptorImpl) jenkins.getDescriptorOrDie(getClass());
+ }
+
+ @TestExtension
+ public static class DescriptorImpl extends Descriptor {}
+
+ public static class Model extends AbstractDescribableImpl {
+
+ @TestExtension
+ public static class DescriptorImpl extends Descriptor {
+
+ boolean touched;
+
+ public ListBoxModel doFillAbcItems(@RelativePath("..") @QueryParameter String name) {
+ assertEquals("Alice", name);
+ touched = true;
+ return new ListBoxModel().add("foo").add("bar");
+ }
+
+ }
+
+ }
+
+}
diff --git a/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java b/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java
index 8c970e8cc9..906adcf6e7 100644
--- a/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java
+++ b/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java
@@ -31,6 +31,7 @@ import hudson.Proc;
import hudson.cli.util.ScriptLoader;
import hudson.model.Node.Mode;
import hudson.model.Slave;
+import hudson.model.User;
import hudson.remoting.Channel;
import hudson.slaves.JNLPLauncher;
import hudson.slaves.RetentionStrategy;
@@ -86,7 +87,7 @@ public class JnlpAccessWithSecuredHudsonTest {
public void anonymousCanAlwaysLoadJARs() throws Exception {
r.jenkins.setNodes(Collections.singletonList(createNewJnlpSlave("test")));
JenkinsRule.WebClient wc = r.createWebClient();
- HtmlPage p = wc.login("alice").goTo("computer/test/");
+ HtmlPage p = wc.withBasicApiToken(User.getById("alice", true)).goTo("computer/test/");
// this fresh WebClient doesn't have a login cookie and represent JNLP launcher
JenkinsRule.WebClient jnlpAgent = r.createWebClient();
diff --git a/test/src/test/java/hudson/cli/BuildCommand2Test.java b/test/src/test/java/hudson/cli/BuildCommand2Test.java
deleted file mode 100644
index 6b6a03743e..0000000000
--- a/test/src/test/java/hudson/cli/BuildCommand2Test.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright 2017 CloudBees, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-package hudson.cli;
-
-import hudson.Launcher;
-import hudson.model.AbstractBuild;
-import hudson.model.BuildListener;
-import hudson.model.FileParameterDefinition;
-import hudson.model.FreeStyleBuild;
-import hudson.model.FreeStyleProject;
-import hudson.model.ParametersDefinitionProperty;
-import hudson.util.OneShotEvent;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import org.junit.ClassRule;
-import org.junit.Test;
-import static org.junit.Assert.*;
-import org.junit.Rule;
-import org.jvnet.hudson.test.BuildWatcher;
-import org.jvnet.hudson.test.Issue;
-import org.jvnet.hudson.test.JenkinsRule;
-import org.jvnet.hudson.test.TestBuilder;
-
-public class BuildCommand2Test {
-
- @ClassRule
- public static BuildWatcher buildWatcher = new BuildWatcher();
-
- @Rule
- public JenkinsRule r = new JenkinsRule();
-
- @Issue("JENKINS-41745")
- @Test
- public void fileParameter() throws Exception {
- FreeStyleProject p = r.createFreeStyleProject("myjob");
- p.addProperty(new ParametersDefinitionProperty(new FileParameterDefinition("file", null)));
- p.getBuildersList().add(new TestBuilder() {
- @Override
- public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
- listener.getLogger().println("Found in my workspace: " + build.getWorkspace().child("file").readToString());
- return true;
- }
- });
- assertThat(new CLICommandInvoker(r, "build").
- withStdin(new ByteArrayInputStream("uploaded content here".getBytes())).
- invokeWithArgs("-f", "-p", "file=", "myjob"),
- CLICommandInvoker.Matcher.succeeded());
- FreeStyleBuild b = p.getBuildByNumber(1);
- assertNotNull(b);
- r.assertLogContains("uploaded content here", b);
- }
-
- /**
- * Just schedules a build and return.
- */
- @Test
- public void async() throws Exception {
- FreeStyleProject p = r.createFreeStyleProject();
- OneShotEvent started = new OneShotEvent();
- OneShotEvent completed = new OneShotEvent();
- p.getBuildersList().add(new TestBuilder() {
- @Override
- public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
- started.signal();
- completed.block();
- return true;
- }
- });
-
- // this should be asynchronous
- CLI cli = new CLI(r.getURL());
- try {
- assertEquals(0, cli.execute("build", p.getName()));
- started.block();
- assertTrue(p.getBuildByNumber(1).isBuilding());
- completed.signal();
- } finally {
- cli.close();
- }
- }
-
-}
diff --git a/test/src/test/java/hudson/cli/BuildCommandTest.java b/test/src/test/java/hudson/cli/BuildCommandTest.java
new file mode 100644
index 0000000000..a55d7c1fe9
--- /dev/null
+++ b/test/src/test/java/hudson/cli/BuildCommandTest.java
@@ -0,0 +1,313 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package hudson.cli;
+
+import hudson.Extension;
+import hudson.Launcher;
+import static hudson.cli.CLICommandInvoker.Matcher.*;
+import hudson.model.AbstractBuild;
+import hudson.model.Action;
+import hudson.model.BuildListener;
+import hudson.model.Executor;
+import hudson.model.FileParameterDefinition;
+import hudson.model.FreeStyleBuild;
+import hudson.model.FreeStyleProject;
+import hudson.model.ParameterDefinition.ParameterDescriptor;
+import hudson.model.ParameterValue;
+import hudson.model.ParametersAction;
+import hudson.model.ParametersDefinitionProperty;
+import hudson.model.Queue.QueueDecisionHandler;
+import hudson.model.Queue.Task;
+import hudson.model.SimpleParameterDefinition;
+import hudson.model.StringParameterDefinition;
+import hudson.model.StringParameterValue;
+import hudson.model.TopLevelItem;
+import hudson.slaves.DumbSlave;
+import hudson.tasks.Shell;
+import hudson.util.OneShotEvent;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import net.sf.json.JSONObject;
+import org.apache.commons.io.output.TeeOutputStream;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.BuildWatcher;
+import org.jvnet.hudson.test.CaptureEnvironmentBuilder;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.TestBuilder;
+import org.jvnet.hudson.test.TestExtension;
+import org.kohsuke.stapler.StaplerRequest;
+
+/**
+ * {@link BuildCommand} test.
+ */
+public class BuildCommandTest {
+
+ @ClassRule
+ public static BuildWatcher buildWatcher = new BuildWatcher();
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ /**
+ * Just schedules a build and return.
+ */
+ @Test
+ public void async() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ OneShotEvent started = new OneShotEvent();
+ OneShotEvent completed = new OneShotEvent();
+ p.getBuildersList().add(new TestBuilder() {
+ @Override
+ public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
+ started.signal();
+ completed.block();
+ return true;
+ }
+ });
+
+ // this should be asynchronous
+ try (CLI cli = new CLI(j.getURL())) {
+ assertEquals(0, cli.execute("build", p.getName()));
+ started.block();
+ assertTrue(p.getBuildByNumber(1).isBuilding());
+ completed.signal();
+ }
+ }
+
+ /**
+ * Tests synchronous execution.
+ */
+ @Test
+ public void sync() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ p.getBuildersList().add(new Shell("sleep 3"));
+
+ try (CLI cli = new CLI(j.getURL())) {
+ cli.execute("build", "-s", p.getName());
+ assertFalse(p.getBuildByNumber(1).isBuilding());
+ }
+ }
+
+ /**
+ * Tests synchronous execution with retried verbose output
+ */
+ @Test
+ public void syncWOutputStreaming() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ p.getBuildersList().add(new Shell("sleep 3"));
+
+ try (CLI cli = new CLI(j.getURL())) {
+ cli.execute("build", "-s", "-v", "-r", "5", p.getName());
+ assertFalse(p.getBuildByNumber(1).isBuilding());
+ }
+ }
+
+ @Test
+ public void parameters() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ p.addProperty(new ParametersDefinitionProperty(new StringParameterDefinition("key", null)));
+
+ try (CLI cli = new CLI(j.getURL())) {
+ cli.execute("build", "-s", "-p", "key=foobar", p.getName());
+ FreeStyleBuild b = j.assertBuildStatusSuccess(p.getBuildByNumber(1));
+ assertEquals("foobar", b.getAction(ParametersAction.class).getParameter("key").getValue());
+ }
+ }
+
+ @Test
+ public void defaultParameters() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ p.addProperty(new ParametersDefinitionProperty(new StringParameterDefinition("key", "default"), new StringParameterDefinition("key2", "default2")));
+
+ try (CLI cli = new CLI(j.getURL())) {
+ cli.execute("build", "-s", "-p", "key=foobar", p.getName());
+ FreeStyleBuild b = j.assertBuildStatusSuccess(p.getBuildByNumber(1));
+ assertEquals("foobar", b.getAction(ParametersAction.class).getParameter("key").getValue());
+ assertEquals("default2", b.getAction(ParametersAction.class).getParameter("key2").getValue());
+ }
+ }
+
+ // TODO randomly fails: Started test0 #1
+ @Test
+ public void consoleOutput() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ try (CLI cli = new CLI(j.getURL())) {
+ ByteArrayOutputStream o = new ByteArrayOutputStream();
+ cli.execute(Arrays.asList("build", "-s", "-v", p.getName()), System.in, new TeeOutputStream(System.out, o), System.err);
+ j.assertBuildStatusSuccess(p.getBuildByNumber(1));
+ assertThat(o.toString(), allOf(containsString("Started from command line by anonymous"), containsString("Finished: SUCCESS")));
+ }
+ }
+
+ // TODO randomly fails: Started test0 #1
+ @Test
+ public void consoleOutputWhenBuildSchedulingRefused() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ try (CLI cli = new CLI(j.getURL())) {
+ ByteArrayOutputStream o = new ByteArrayOutputStream();
+ cli.execute(Arrays.asList("build", "-s", "-v", p.getName()), System.in, System.out, new TeeOutputStream(System.err, o));
+ assertThat(o.toString(), containsString(BuildCommand.BUILD_SCHEDULING_REFUSED));
+ }
+ }
+ // <=>
+ @TestExtension("consoleOutputWhenBuildSchedulingRefused")
+ public static class UnschedulingVetoer extends QueueDecisionHandler {
+ @Override
+ public boolean shouldSchedule(Task task, List actions) {
+ return false;
+ }
+ }
+
+ @Test
+ public void refuseToBuildDisabledProject() throws Exception {
+ FreeStyleProject project = j.createFreeStyleProject("the-project");
+ project.disable();
+ CLICommandInvoker invoker = new CLICommandInvoker(j, new BuildCommand());
+ CLICommandInvoker.Result result = invoker.invokeWithArgs("the-project");
+
+ assertThat(result, failedWith(4));
+ assertThat(result.stderr(), containsString("ERROR: Cannot build the-project because it is disabled."));
+ assertNull("Project should not be built", project.getBuildByNumber(1));
+ }
+
+ @Test
+ public void refuseToBuildNewlyCopiedProject() throws Exception {
+ FreeStyleProject original = j.createFreeStyleProject("original");
+ FreeStyleProject newOne = (FreeStyleProject) j.jenkins.copy(original, "new-one");
+ CLICommandInvoker invoker = new CLICommandInvoker(j, new BuildCommand());
+ CLICommandInvoker.Result result = invoker.invokeWithArgs("new-one");
+
+ assertThat(result, failedWith(4));
+ assertThat(result.stderr(), containsString("ERROR: Cannot build new-one because its configuration has not been saved."));
+ assertNull("Project should not be built", newOne.getBuildByNumber(1));
+ }
+
+ @Test
+ public void correctlyParseMapValuesContainingEqualsSign() throws Exception {
+ FreeStyleProject project = j.createFreeStyleProject("the-project");
+ project.addProperty(new ParametersDefinitionProperty(new StringParameterDefinition("expr", null)));
+
+ CLICommandInvoker invoker = new CLICommandInvoker(j, new BuildCommand());
+ CLICommandInvoker.Result result = invoker.invokeWithArgs("the-project", "-p", "expr=a=b", "-s");
+
+ assertThat(result, succeeded());
+ assertEquals("a=b", project.getBuildByNumber(1).getBuildVariables().get("expr"));
+ }
+
+ @Issue("JENKINS-15094")
+ @Test
+ public void executorsAliveOnParameterWithNullDefaultValue() throws Exception {
+ DumbSlave slave = j.createSlave();
+ FreeStyleProject project = j.createFreeStyleProject("foo");
+ project.setAssignedNode(slave);
+
+ // Create test parameter with Null default value
+ NullDefaultValueParameterDefinition nullDefaultDefinition = new NullDefaultValueParameterDefinition();
+ ParametersDefinitionProperty pdp = new ParametersDefinitionProperty(
+ new StringParameterDefinition("string", "defaultValue", "description"),
+ nullDefaultDefinition);
+ project.addProperty(pdp);
+ CaptureEnvironmentBuilder builder = new CaptureEnvironmentBuilder();
+ project.getBuildersList().add(builder);
+
+ // Warmup
+ j.buildAndAssertSuccess(project);
+
+ for (Executor exec : slave.toComputer().getExecutors()) {
+ assertTrue("Executor has died before the test start: " + exec, exec.isActive());
+ }
+
+ // Create CLI & run command
+ CLICommandInvoker invoker = new CLICommandInvoker(j, new BuildCommand());
+ CLICommandInvoker.Result result = invoker.invokeWithArgs("foo", "-p", "string=value");
+ assertThat(result, failedWith(2));
+ assertThat(result.stderr(), containsString("ERROR: No default value for the parameter \'FOO\'."));
+
+ Thread.sleep(5000); // Give the job 5 seconds to be submitted
+ assertNull("Build should not be scheduled", j.jenkins.getQueue().getItem(project));
+ assertNull("Build should not be scheduled", project.getBuildByNumber(2));
+
+ // Check executors health after a timeout
+ for (Executor exec : slave.toComputer().getExecutors()) {
+ assertTrue("Executor is dead: " + exec, exec.isActive());
+ }
+ }
+
+ public static final class NullDefaultValueParameterDefinition extends SimpleParameterDefinition {
+
+ /*package*/ NullDefaultValueParameterDefinition() {
+ super("FOO", "Always null default value");
+ }
+
+ @Override
+ public ParameterValue createValue(String value) {
+ return new StringParameterValue("FOO", "BAR");
+ }
+
+ @Override
+ public ParameterValue createValue(StaplerRequest req, JSONObject jo) {
+ return createValue("BAR");
+ }
+
+ @Override
+ public ParameterValue getDefaultParameterValue() {
+ return null; // Equals to super.getDefaultParameterValue();
+ }
+
+ @Extension
+ public static class DescriptorImpl extends ParameterDescriptor {}
+
+ }
+
+ @Issue("JENKINS-41745")
+ @Test
+ public void fileParameter() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject("myjob");
+ p.addProperty(new ParametersDefinitionProperty(new FileParameterDefinition("file", null)));
+ p.getBuildersList().add(new TestBuilder() {
+ @Override
+ public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
+ listener.getLogger().println("Found in my workspace: " + build.getWorkspace().child("file").readToString());
+ return true;
+ }
+ });
+ assertThat(new CLICommandInvoker(j, "build").
+ withStdin(new ByteArrayInputStream("uploaded content here".getBytes())).
+ invokeWithArgs("-f", "-p", "file=", "myjob"),
+ CLICommandInvoker.Matcher.succeeded());
+ FreeStyleBuild b = p.getBuildByNumber(1);
+ assertNotNull(b);
+ j.assertLogContains("uploaded content here", b);
+ }
+
+}
diff --git a/test/src/test/java/hudson/cli/SetBuildParameterCommandTest.java b/test/src/test/java/hudson/cli/SetBuildParameterCommandTest.java
new file mode 100644
index 0000000000..937abe4628
--- /dev/null
+++ b/test/src/test/java/hudson/cli/SetBuildParameterCommandTest.java
@@ -0,0 +1,88 @@
+package hudson.cli;
+
+import com.google.common.collect.ImmutableMap;
+import hudson.Functions;
+import hudson.Launcher;
+import hudson.model.AbstractBuild;
+import hudson.model.BuildListener;
+import hudson.model.FreeStyleBuild;
+import hudson.model.FreeStyleProject;
+import hudson.model.ParametersAction;
+import hudson.model.ParametersDefinitionProperty;
+import hudson.model.ParameterDefinition;
+import hudson.model.Result;
+import hudson.model.StringParameterDefinition;
+import hudson.tasks.BatchFile;
+import hudson.tasks.Builder;
+import hudson.tasks.Shell;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import static org.junit.Assert.*;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.BuildWatcher;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.TestBuilder;
+
+public class SetBuildParameterCommandTest {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ @ClassRule
+ public static BuildWatcher buildWatcher = new BuildWatcher();
+
+ @Test
+ public void cli() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ p.getBuildersList().add(new TestBuilder() {
+ @Override
+ public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
+ URL jar = j.jenkins.servletContext.getResource("/WEB-INF/jenkins-cli.jar");
+ build.getWorkspace().child("cli.jar").copyFrom(jar);
+
+ return true;
+ }
+ });
+ List pd = Arrays.asList(new StringParameterDefinition("a", ""), new StringParameterDefinition("b", ""));
+ p.addProperty(new ParametersDefinitionProperty(pd));
+ p.getBuildersList().add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter a b"));
+ p.getBuildersList().add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter a x"));
+ p.getBuildersList().add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter b y"));
+
+ Map r = new TreeMap<>();
+
+ FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
+ b.getAction(ParametersAction.class).getParameters().forEach(v -> r.put(v.getName(), v.getValue()));
+
+ assertEquals(ImmutableMap.of("a", "x", "b", "y"), r);
+
+ if (Functions.isWindows()) {
+ p.getBuildersList().add(new BatchFile("set BUILD_NUMBER=1\r\njava -jar cli.jar -remoting -noKeyAuth set-build-parameter a b"));
+ } else {
+ p.getBuildersList().add(new Shell("BUILD_NUMBER=1 java -jar cli.jar -remoting -noKeyAuth set-build-parameter a b"));
+ }
+ FreeStyleBuild b2 = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get());
+ j.assertLogContains("#1 is not currently being built", b2);
+ r.clear();
+ b.getAction(ParametersAction.class).getParameters().forEach(v -> r.put(v.getName(), v.getValue()));
+ assertEquals(ImmutableMap.of("a", "x", "b", "y"), r);
+ }
+
+ //TODO: determine if this should be pulled out into JenkinsRule or something
+ /**
+ * Create a script based builder (either Shell or BatchFile) depending on
+ * platform
+ * @param script the contents of the script to run
+ * @return A Builder instance of either Shell or BatchFile
+ */
+ private Builder createScriptBuilder(String script) {
+ return Functions.isWindows() ? new BatchFile(script) : new Shell(script);
+ }
+
+}
diff --git a/test/src/test/java/hudson/diagnosis/HudsonHomeDiskUsageMonitorTest.java b/test/src/test/java/hudson/diagnosis/HudsonHomeDiskUsageMonitorTest.java
index d416b3e451..3b3da3b177 100644
--- a/test/src/test/java/hudson/diagnosis/HudsonHomeDiskUsageMonitorTest.java
+++ b/test/src/test/java/hudson/diagnosis/HudsonHomeDiskUsageMonitorTest.java
@@ -11,10 +11,7 @@ import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import hudson.model.User;
import hudson.security.GlobalMatrixAuthorizationStrategy;
-import hudson.security.HudsonPrivateSecurityRealm;
-import hudson.security.Permission;
import jenkins.model.Jenkins;
-import org.acegisecurity.context.SecurityContextHolder;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
@@ -25,6 +22,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import java.io.IOException;
+import java.net.URL;
import java.util.Collections;
/**
@@ -73,28 +71,33 @@ public class HudsonHomeDiskUsageMonitorTest {
auth.add(Jenkins.READ, "users");
j.jenkins.setAuthorizationStrategy(auth);
- WebRequest request = new WebRequest(wc.createCrumbedUrl("administrativeMonitor/hudsonHomeIsFull/act"), HttpMethod.POST);
+ User bob = User.getById("bob", true);
+ User administrator = User.getById("administrator", true);
+
+ WebRequest request = new WebRequest(new URL(wc.getContextPath() + "administrativeMonitor/hudsonHomeIsFull/act"), HttpMethod.POST);
NameValuePair param = new NameValuePair("no", "true");
request.setRequestParameters(Collections.singletonList(param));
HudsonHomeDiskUsageMonitor mon = HudsonHomeDiskUsageMonitor.get();
+ wc.withBasicApiToken(bob);
try {
- wc.login("bob");
wc.getPage(request);
+ fail();
} catch (FailingHttpStatusCodeException e) {
assertEquals(403, e.getStatusCode());
}
assertTrue(mon.isEnabled());
+ WebRequest requestReadOnly = new WebRequest(new URL(wc.getContextPath() + "administrativeMonitor/hudsonHomeIsFull"), HttpMethod.GET);
try {
- WebRequest getIndex = new WebRequest(wc.createCrumbedUrl("administrativeMonitor/hudsonHomeIsFull"), HttpMethod.GET);
- wc.getPage(getIndex);
+ wc.getPage(requestReadOnly);
+ fail();
} catch (FailingHttpStatusCodeException e) {
assertEquals(403, e.getStatusCode());
}
- wc.login("administrator");
+ wc.withBasicApiToken(administrator);
wc.getPage(request);
assertFalse(mon.isEnabled());
diff --git a/test/src/test/java/hudson/model/AbstractItemTest.java b/test/src/test/java/hudson/model/AbstractItemTest.java
new file mode 100644
index 0000000000..267f8f3524
--- /dev/null
+++ b/test/src/test/java/hudson/model/AbstractItemTest.java
@@ -0,0 +1,43 @@
+package hudson.model;
+
+import java.io.File;
+import jenkins.model.Jenkins;
+import org.apache.commons.io.FileUtils;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.JenkinsRule;
+
+public class AbstractItemTest {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ /**
+ * Tests the reload functionality
+ */
+ @Test
+ public void reload() throws Exception {
+ Jenkins jenkins = j.jenkins;
+ FreeStyleProject p = jenkins.createProject(FreeStyleProject.class, "foo");
+ p.setDescription("Hello World");
+
+ FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
+ b.setDescription("This is my build");
+
+ // update on disk representation
+ File f = p.getConfigFile().getFile();
+ FileUtils.writeStringToFile(f, FileUtils.readFileToString(f).replaceAll("Hello World", "Good Evening"));
+
+ // reload away
+ p.doReload();
+
+ assertEquals("Good Evening", p.getDescription());
+
+ FreeStyleBuild b2 = p.getBuildByNumber(1);
+
+ assertNotEquals(b, b2); // should be different object
+ assertEquals(b.getDescription(), b2.getDescription()); // but should have the same properties
+ }
+
+}
diff --git a/test/src/test/java/hudson/model/AbstractProject2Test.java b/test/src/test/java/hudson/model/AbstractProject2Test.java
deleted file mode 100644
index a5f330ca43..0000000000
--- a/test/src/test/java/hudson/model/AbstractProject2Test.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright 2017 CloudBees, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-package hudson.model;
-
-import hudson.tasks.BuildTrigger;
-import java.util.Collections;
-import jenkins.model.Jenkins;
-import static org.hamcrest.Matchers.*;
-import org.junit.Test;
-import static org.junit.Assert.*;
-import org.junit.Rule;
-import org.jvnet.hudson.test.Issue;
-import org.jvnet.hudson.test.JenkinsRule;
-import org.jvnet.hudson.test.MockAuthorizationStrategy;
-
-// TODO merge with AbstractProjectTest when converted from Groovy to Java
-public class AbstractProject2Test {
-
- @Rule
- public JenkinsRule r = new JenkinsRule();
-
- @Issue("SECURITY-617")
- @Test
- public void upstreamDownstreamExportApi() throws Exception {
- FreeStyleProject us = r.createFreeStyleProject("upstream-project");
- FreeStyleProject ds = r.createFreeStyleProject("downstream-project");
- us.getPublishersList().add(new BuildTrigger(Collections.singleton(ds), Result.SUCCESS));
- r.jenkins.rebuildDependencyGraph();
- assertEquals(Collections.singletonList(ds), us.getDownstreamProjects());
- assertEquals(Collections.singletonList(us), ds.getUpstreamProjects());
- r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
- r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().
- grant(Jenkins.READ).everywhere().toEveryone().
- grant(Item.READ).everywhere().to("alice").
- grant(Item.READ).onItems(us).to("bob").
- grant(Item.READ).onItems(ds).to("charlie"));
- String api = r.createWebClient().login("alice").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString();
- System.out.println(api);
- assertThat(api, containsString("downstream-project"));
- api = r.createWebClient().login("alice").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString();
- System.out.println(api);
- assertThat(api, containsString("upstream-project"));
- api = r.createWebClient().login("bob").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString();
- System.out.println(api);
- assertThat(api, not(containsString("downstream-project")));
- api = r.createWebClient().login("charlie").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString();
- System.out.println(api);
- assertThat(api, not(containsString("upstream-project")));
- }
-
-}
diff --git a/test/src/test/java/hudson/model/AbstractProjectTest.java b/test/src/test/java/hudson/model/AbstractProjectTest.java
new file mode 100644
index 0000000000..8ba1982b11
--- /dev/null
+++ b/test/src/test/java/hudson/model/AbstractProjectTest.java
@@ -0,0 +1,646 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.model;
+
+import com.gargoylesoftware.htmlunit.ElementNotFoundException;
+import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.HttpMethod;
+import com.gargoylesoftware.htmlunit.WebRequest;
+import com.gargoylesoftware.htmlunit.WebResponse;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlInput;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import hudson.FilePath;
+import hudson.Functions;
+import hudson.Launcher;
+import hudson.Util;
+import hudson.maven.MavenModuleSet;
+import hudson.scm.NullSCM;
+import hudson.scm.SCM;
+import hudson.scm.SCMDescriptor;
+import hudson.security.GlobalMatrixAuthorizationStrategy;
+import hudson.tasks.ArtifactArchiver;
+import hudson.tasks.BatchFile;
+import hudson.tasks.BuildTrigger;
+import hudson.tasks.Shell;
+import hudson.triggers.SCMTrigger;
+import hudson.triggers.TimerTrigger;
+import hudson.triggers.Trigger;
+import hudson.triggers.TriggerDescriptor;
+import hudson.util.OneShotEvent;
+import hudson.util.StreamTaskListener;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Collections;
+import java.util.ResourceBundle;
+import java.util.Vector;
+import java.util.concurrent.Future;
+import jenkins.model.Jenkins;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.MockAuthorizationStrategy;
+import org.jvnet.hudson.test.MockFolder;
+import org.jvnet.hudson.test.TestExtension;
+import org.jvnet.hudson.test.TestPluginManager;
+import org.jvnet.hudson.test.recipes.PresetData;
+import org.jvnet.hudson.test.recipes.PresetData.DataSet;
+import org.kohsuke.args4j.CmdLineException;
+
+public class AbstractProjectTest {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ @Test
+ public void configRoundtrip() throws Exception {
+ FreeStyleProject project = j.createFreeStyleProject();
+ Label l = j.jenkins.getLabel("foo && bar");
+ project.setAssignedLabel(l);
+ j.configRoundtrip(project);
+
+ assertEquals(l, project.getAssignedLabel());
+ }
+
+ /**
+ * Tests the workspace deletion.
+ */
+ @Test
+ public void wipeWorkspace() throws Exception {
+ FreeStyleProject project = j.createFreeStyleProject();
+ project.getBuildersList().add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("echo hello"));
+
+ FreeStyleBuild b = project.scheduleBuild2(0).get();
+
+ assertTrue("Workspace should exist by now", b.getWorkspace().exists());
+
+ project.doDoWipeOutWorkspace();
+
+ assertFalse("Workspace should be gone by now", b.getWorkspace().exists());
+ }
+
+ /**
+ * Makes sure that the workspace deletion is protected.
+ */
+ @Test
+ @PresetData(DataSet.NO_ANONYMOUS_READACCESS)
+ public void wipeWorkspaceProtected() throws Exception {
+ FreeStyleProject project = j.createFreeStyleProject();
+ project.getBuildersList().add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("echo hello"));
+
+ FreeStyleBuild b = project.scheduleBuild2(0).get();
+
+ assert b.getWorkspace().exists() : "Workspace should exist by now";
+
+ // make sure that the action link is protected
+ JenkinsRule.WebClient wc = j.createWebClient();
+ try {
+ wc.getPage(new WebRequest(new URL(wc.getContextPath() + project.getUrl() + "doWipeOutWorkspace"), HttpMethod.POST));
+ fail("Expected HTTP status code 403");
+ } catch (FailingHttpStatusCodeException e) {
+ assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getStatusCode());
+ }
+ }
+
+ /**
+ * Makes sure that the workspace deletion link is not provided when the user
+ * doesn't have an access.
+ */
+ @Test
+ @PresetData(DataSet.ANONYMOUS_READONLY)
+ public void wipeWorkspaceProtected2() throws Exception {
+ ((GlobalMatrixAuthorizationStrategy) j.jenkins.getAuthorizationStrategy()).add(AbstractProject.WORKSPACE, "anonymous");
+
+ // make sure that the deletion is protected in the same way
+ wipeWorkspaceProtected();
+
+ // there shouldn't be any "wipe out workspace" link for anonymous user
+ JenkinsRule.WebClient webClient = j.createWebClient();
+ HtmlPage page = webClient.getPage(j.jenkins.getItem("test0"));
+
+ page = (HtmlPage) page.getAnchorByText("Workspace").click();
+ try {
+ String wipeOutLabel = ResourceBundle.getBundle("hudson/model/AbstractProject/sidepanel").getString("Wipe Out Workspace");
+ page.getAnchorByText(wipeOutLabel);
+ fail("shouldn't find a link");
+ } catch (ElementNotFoundException e) {
+ // OK
+ }
+ }
+
+ /**
+ * Tests the <optionalBlock @field> round trip behavior by using
+ * {@link AbstractProject#concurrentBuild}
+ */
+ @Test
+ public void optionalBlockDataBindingRoundtrip() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ for (boolean b : new boolean[] {true, false}) {
+ p.setConcurrentBuild(b);
+ j.submit(j.createWebClient().getPage(p, "configure").getFormByName("config"));
+ assertEquals(b, p.isConcurrentBuild());
+ }
+ }
+
+ /**
+ * Tests round trip configuration of the blockBuildWhenUpstreamBuilding
+ * field
+ */
+ @Test
+ @Issue("JENKINS-4423")
+ public void configuringBlockBuildWhenUpstreamBuildingRoundtrip() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ p.setBlockBuildWhenUpstreamBuilding(false);
+
+ HtmlForm form = j.createWebClient().getPage(p, "configure").getFormByName("config");
+ HtmlInput input = form.getInputByName("blockBuildWhenUpstreamBuilding");
+ assertFalse("blockBuildWhenUpstreamBuilding check box is checked.", input.isChecked());
+
+ input.setChecked(true);
+ j.submit(form);
+ assertTrue("blockBuildWhenUpstreamBuilding was not updated from configuration form", p.blockBuildWhenUpstreamBuilding());
+
+ form = j.createWebClient().getPage(p, "configure").getFormByName("config");
+ input = form.getInputByName("blockBuildWhenUpstreamBuilding");
+ assert input.isChecked() : "blockBuildWhenUpstreamBuilding check box is not checked.";
+ }
+
+ /**
+ * Unless the concurrent build option is enabled, polling and build should
+ * be mutually exclusive to avoid allocating unnecessary workspaces.
+ */
+ @Test
+ @Issue("JENKINS-4202")
+ public void pollingAndBuildExclusion() throws Exception {
+ final OneShotEvent sync = new OneShotEvent();
+
+ final FreeStyleProject p = j.createFreeStyleProject();
+ FreeStyleBuild b1 = j.buildAndAssertSuccess(p);
+
+ p.setScm(new NullSCM() {
+ @Override
+ public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener) {
+ try {
+ sync.block();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+ /**
+ * Don't write 'this', so that subtypes can be implemented as
+ * anonymous class.
+ */
+ private Object writeReplace() {
+ return new Object();
+ }
+
+ @Override
+ public boolean requiresWorkspaceForPolling() {
+ return true;
+ }
+ @Override
+ public SCMDescriptor> getDescriptor() {
+ return new SCMDescriptor(null) {
+ };
+ }
+ });
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ p.pollSCMChanges(StreamTaskListener.fromStdout());
+ }
+ };
+ try {
+ t.start();
+ Future f = p.scheduleBuild2(0);
+
+ // add a bit of delay to make sure that the blockage is happening
+ Thread.sleep(3000);
+
+ // release the polling
+ sync.signal();
+
+ FreeStyleBuild b2 = j.assertBuildStatusSuccess(f);
+
+ // they should have used the same workspace.
+ assertEquals(b1.getWorkspace(), b2.getWorkspace());
+ } finally {
+ t.interrupt();
+ }
+ }
+
+ @Test
+ @Issue("JENKINS-1986")
+ public void buildSymlinks() throws Exception {
+ Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows());
+
+ FreeStyleProject job = j.createFreeStyleProject();
+ job.getBuildersList().add(new Shell("echo \"Build #$BUILD_NUMBER\"\n"));
+ FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserCause()).get();
+ File lastSuccessful = new File(job.getRootDir(), "lastSuccessful"),
+ lastStable = new File(job.getRootDir(), "lastStable");
+ // First build creates links
+ assertSymlinkForBuild(lastSuccessful, 1);
+ assertSymlinkForBuild(lastStable, 1);
+ FreeStyleBuild build2 = job.scheduleBuild2(0, new Cause.UserCause()).get();
+ // Another build updates links
+ assertSymlinkForBuild(lastSuccessful, 2);
+ assertSymlinkForBuild(lastStable, 2);
+ // Delete latest build should update links
+ build2.delete();
+ assertSymlinkForBuild(lastSuccessful, 1);
+ assertSymlinkForBuild(lastStable, 1);
+ // Delete all builds should remove links
+ build.delete();
+ assertFalse("lastSuccessful link should be removed", lastSuccessful.exists());
+ assertFalse("lastStable link should be removed", lastStable.exists());
+ }
+
+ private static void assertSymlinkForBuild(File file, int buildNumber)
+ throws IOException, InterruptedException {
+ assert file.exists() : "should exist and point to something that exists";
+ assert Util.isSymlink(file) : "should be symlink";
+ String s = FileUtils.readFileToString(new File(file, "log"));
+ assert s.contains("Build #" + buildNumber + "\n") : "link should point to build #$buildNumber, but link was: ${Util.resolveSymlink(file, TaskListener.NULL)}\nand log was:\n$s";
+ }
+
+ @Test
+ @Issue("JENKINS-2543")
+ public void symlinkForPostBuildFailure() throws Exception {
+ Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows());
+
+ // Links should be updated after post-build actions when final build result is known
+ FreeStyleProject job = j.createFreeStyleProject();
+ job.getBuildersList().add(new Shell("echo \"Build #$BUILD_NUMBER\"\n"));
+ FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserCause()).get();
+ assert Result.SUCCESS == build.getResult();
+ File lastSuccessful = new File(job.getRootDir(), "lastSuccessful"),
+ lastStable = new File(job.getRootDir(), "lastStable");
+ // First build creates links
+ assertSymlinkForBuild(lastSuccessful, 1);
+ assertSymlinkForBuild(lastStable, 1);
+ // Archive artifacts that don't exist to create failure in post-build action
+ job.getPublishersList().add(new ArtifactArchiver("*.foo", "", false, false));
+ build = job.scheduleBuild2(0, new Cause.UserCause()).get();
+ assert Result.FAILURE == build.getResult();
+ // Links should not be updated since build failed
+ assertSymlinkForBuild(lastSuccessful, 1);
+ assertSymlinkForBuild(lastStable, 1);
+ }
+
+ /* TODO too slow, seems capable of causing testWorkspaceLock to time out:
+ @Test
+ @Issue("JENKINS-15156")
+ public void testGetBuildAfterGC() {
+ FreeStyleProject job = j.createFreeStyleProject();
+ job.scheduleBuild2(0, new Cause.UserIdCause()).get();
+ j.jenkins.queue.clearLeftItems();
+ MemoryAssert.assertGC(new WeakReference(job.getLastBuild()));
+ assert job.lastBuild != null;
+ }
+ */
+ @Test
+ @Issue("JENKINS-17137")
+ public void externalBuildDirectorySymlinks() throws Exception {
+ Assume.assumeFalse(Functions.isWindows()); // symlinks may not be available
+ HtmlForm form = j.createWebClient().goTo("configure").getFormByName("config");
+ File builds = j.createTmpDir();
+ form.getInputByName("_.rawBuildsDir").setValueAttribute(builds.toString() + "/${ITEM_FULL_NAME}");
+ j.submit(form);
+ assertEquals(builds.toString() + "/${ITEM_FULL_NAME}", j.jenkins.getRawBuildsDir());
+ FreeStyleProject p = j.jenkins.createProject(MockFolder.class, "d").createProject(FreeStyleProject.class, "p");
+ FreeStyleBuild b1 = p.scheduleBuild2(0).get();
+ File link = new File(p.getRootDir(), "lastStable");
+ assertTrue(link.exists());
+ assertEquals(resolveAll(link).getAbsolutePath(), b1.getRootDir().getAbsolutePath());
+ FreeStyleBuild b2 = p.scheduleBuild2(0).get();
+ assertTrue(link.exists());
+ assertEquals(resolveAll(link).getAbsolutePath(), b2.getRootDir().getAbsolutePath());
+ b2.delete();
+ assertTrue(link.exists());
+ assertEquals(resolveAll(link).getAbsolutePath(), b1.getRootDir().getAbsolutePath());
+ b1.delete();
+ assertFalse(link.exists());
+ }
+
+ private File resolveAll(File link) throws InterruptedException, IOException {
+ while (true) {
+ File f = Util.resolveSymlinkToFile(link);
+ if (f == null) {
+ return link;
+ }
+ link = f;
+ }
+ }
+
+ @Test
+ @Issue("JENKINS-17138")
+ public void externalBuildDirectoryRenameDelete() throws Exception {
+ HtmlForm form = j.createWebClient().goTo("configure").getFormByName("config");
+ File builds = j.createTmpDir();
+ form.getInputByName("_.rawBuildsDir").setValueAttribute(builds.toString() + "/${ITEM_FULL_NAME}");
+ j.submit(form);
+ assertEquals(builds.toString() + "/${ITEM_FULL_NAME}", j.jenkins.getRawBuildsDir());
+ FreeStyleProject p = j.jenkins.createProject(MockFolder.class, "d").createProject(FreeStyleProject.class, "prj");
+ FreeStyleBuild b = p.scheduleBuild2(0).get();
+ File oldBuildDir = new File(builds, "d/prj");
+ assertEquals(new File(oldBuildDir, b.getId()), b.getRootDir());
+ assertTrue(b.getRootDir().isDirectory());
+ p.renameTo("proj");
+ File newBuildDir = new File(builds, "d/proj");
+ assertEquals(new File(newBuildDir, b.getId()), b.getRootDir());
+ assertTrue(b.getRootDir().isDirectory());
+ p.delete();
+ assertFalse(b.getRootDir().isDirectory());
+ }
+
+ @Test
+ @Issue("JENKINS-18678")
+ public void renameJobLostBuilds() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject("initial");
+ j.assertBuildStatusSuccess(p.scheduleBuild2(0));
+ assertEquals(1, p.getBuilds().size());
+ p.renameTo("edited");
+ p._getRuns().purgeCache();
+ assertEquals(1, p.getBuilds().size());
+ MockFolder d = j.jenkins.createProject(MockFolder.class, "d");
+ Items.move(p, d);
+ assertEquals(p, j.jenkins.getItemByFullName("d/edited"));
+ p._getRuns().purgeCache();
+ assertEquals(1, p.getBuilds().size());
+ d.renameTo("d2");
+ p = j.jenkins.getItemByFullName("d2/edited", FreeStyleProject.class);
+ p._getRuns().purgeCache();
+ assertEquals(1, p.getBuilds().size());
+ }
+
+ @Test
+ @Issue("JENKINS-17575")
+ public void deleteRedirect() throws Exception {
+ j.createFreeStyleProject("j1");
+ assertEquals("", deleteRedirectTarget("job/j1"));
+ j.createFreeStyleProject("j2");
+ Jenkins.getInstance().addView(new AllView("v1"));
+ assertEquals("view/v1/", deleteRedirectTarget("view/v1/job/j2"));
+ MockFolder d = Jenkins.getInstance().createProject(MockFolder.class, "d");
+ d.addView(new AllView("v2"));
+ for (String n : new String[] {"j3", "j4", "j5"}) {
+ d.createProject(FreeStyleProject.class, n);
+ }
+ assertEquals("job/d/", deleteRedirectTarget("job/d/job/j3"));
+ assertEquals("job/d/view/v2/", deleteRedirectTarget("job/d/view/v2/job/j4"));
+ assertEquals("view/v1/job/d/", deleteRedirectTarget("view/v1/job/d/job/j5"));
+ assertEquals("view/v1/", deleteRedirectTarget("view/v1/job/d")); // JENKINS-23375
+ }
+
+ private String deleteRedirectTarget(String job) throws Exception {
+ JenkinsRule.WebClient wc = j.createWebClient();
+ String base = wc.getContextPath();
+ String loc = wc.getPage(wc.addCrumb(new WebRequest(new URL(base + job + "/doDelete"), HttpMethod.POST))).getUrl().toString();
+ assert loc.startsWith(base) : loc;
+ return loc.substring(base.length());
+ }
+
+ @Test
+ @Issue("JENKINS-18407")
+ public void queueSuccessBehavior() throws Exception {
+ // prevent any builds to test the behaviour
+ j.jenkins.setNumExecutors(0);
+
+ FreeStyleProject p = j.createFreeStyleProject();
+ Future f = p.scheduleBuild2(0);
+ assertNotNull(f);
+ Future g = p.scheduleBuild2(0);
+ assertEquals(f, g);
+
+ p.makeDisabled(true);
+ assertNull(p.scheduleBuild2(0));
+ }
+
+ /**
+ * Do the same as {@link #testQueueSuccessBehavior()} but over HTTP
+ */
+ @Test
+ @Issue("JENKINS-18407")
+ public void queueSuccessBehaviorOverHTTP() throws Exception {
+ // prevent any builds to test the behaviour
+ j.jenkins.setNumExecutors(0);
+
+ FreeStyleProject p = j.createFreeStyleProject();
+ JenkinsRule.WebClient wc = j.createWebClient();
+
+ WebResponse rsp = wc.getPage(j.getURL() + p.getUrl() + "build").getWebResponse();
+ assertEquals(201, rsp.getStatusCode());
+ assertNotNull(rsp.getResponseHeaderValue("Location"));
+
+ WebResponse rsp2 = wc.getPage(j.getURL() + p.getUrl() + "build").getWebResponse();
+ assertEquals(201, rsp2.getStatusCode());
+ assertEquals(rsp.getResponseHeaderValue("Location"), rsp2.getResponseHeaderValue("Location"));
+
+ p.makeDisabled(true);
+
+ try {
+ wc.getPage(j.getURL() + p.getUrl() + "build");
+ fail();
+ } catch (FailingHttpStatusCodeException e) {
+ // request should fail
+ }
+ }
+
+ /**
+ * We used to store {@link AbstractProject#triggers} as {@link Vector}, so
+ * make sure we can still read back the configuration from that.
+ */
+ @Test
+ public void vectorTriggers() throws Exception {
+ AbstractProject, ?> p = (AbstractProject) j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml"));
+ assertEquals(1, p.triggers().size());
+ Trigger> t = p.triggers().get(0);
+ assertEquals(SCMTrigger.class, t.getClass());
+ assertEquals("*/10 * * * *", t.getSpec());
+ }
+
+ @Test
+ @Issue("JENKINS-18813")
+ public void removeTrigger() throws Exception {
+ AbstractProject, ?> p = (AbstractProject) j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml"));
+
+ TriggerDescriptor SCM_TRIGGER_DESCRIPTOR = (TriggerDescriptor) j.jenkins.getDescriptorOrDie(SCMTrigger.class);
+ p.removeTrigger(SCM_TRIGGER_DESCRIPTOR);
+ assertEquals(0, p.triggers().size());
+ }
+
+ @Test
+ @Issue("JENKINS-18813")
+ public void addTriggerSameType() throws Exception {
+ AbstractProject, ?> p = (AbstractProject) j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml"));
+
+ SCMTrigger newTrigger = new SCMTrigger("H/5 * * * *");
+ p.addTrigger(newTrigger);
+
+ assertEquals(1, p.triggers().size());
+ Trigger> t = p.triggers().get(0);
+ assertEquals(SCMTrigger.class, t.getClass());
+ assertEquals("H/5 * * * *", t.getSpec());
+ }
+
+ @Test
+ @Issue("JENKINS-18813")
+ public void addTriggerDifferentType() throws Exception {
+ AbstractProject, ?> p = (AbstractProject) j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml"));
+
+ TimerTrigger newTrigger = new TimerTrigger("20 * * * *");
+ p.addTrigger(newTrigger);
+
+ assertEquals(2, p.triggers().size());
+ Trigger> t = p.triggers().get(1);
+ assertEquals(newTrigger, t);
+ }
+
+ /**
+ * Trying to POST to config.xml by a different job type should fail.
+ */
+ @Test
+ public void configDotXmlSubmissionToDifferentType() throws Exception {
+ TestPluginManager tpm = (TestPluginManager) j.jenkins.pluginManager;
+ tpm.installDetachedPlugin("javadoc");
+ tpm.installDetachedPlugin("junit");
+ tpm.installDetachedPlugin("display-url-api");
+ tpm.installDetachedPlugin("mailer");
+ tpm.installDetachedPlugin("maven-plugin");
+
+ j.jenkins.setCrumbIssuer(null);
+ FreeStyleProject p = j.createFreeStyleProject();
+
+ HttpURLConnection con = postConfigDotXml(p, " ");
+
+ // this should fail with a type mismatch error
+ // the error message should report both what was submitted and what was expected
+ assertEquals(500, con.getResponseCode());
+ String msg = IOUtils.toString(con.getErrorStream());
+ System.out.println(msg);
+ assertThat(msg, allOf(containsString(FreeStyleProject.class.getName()), containsString(MavenModuleSet.class.getName())));
+
+ // control. this should work
+ con = postConfigDotXml(p, " ");
+ assertEquals(200, con.getResponseCode());
+ }
+
+ private HttpURLConnection postConfigDotXml(FreeStyleProject p, String xml) throws Exception {
+ HttpURLConnection con = (HttpURLConnection) new URL(j.getURL(), "job/" + p.getName() + "/config.xml").openConnection();
+ con.setRequestMethod("POST");
+ con.setRequestProperty("Content-Type", "application/xml");
+ con.setDoOutput(true);
+ try (OutputStream s = con.getOutputStream()) {
+ s.write(xml.getBytes());
+ }
+ return con;
+ }
+
+ @Test
+ @Issue("JENKINS-27549")
+ public void loadingWithNPEOnTriggerStart() throws Exception {
+ AbstractProject, ?> project = (AbstractProject) j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/npeTrigger.xml"));
+
+ assertEquals(1, project.triggers().size());
+ }
+
+ @Test
+ @Issue("JENKINS-30742")
+ public void resolveForCLI() throws Exception {
+ try {
+ AbstractProject, ?> not_found = AbstractProject.resolveForCLI("never_created");
+ fail("Exception should occur before!");
+ } catch (CmdLineException e) {
+ assertEquals("No such job \u2018never_created\u2019 exists.", e.getMessage());
+ }
+
+ AbstractProject, ?> project = j.jenkins.createProject(FreeStyleProject.class, "never_created");
+ try {
+ AbstractProject, ?> not_found = AbstractProject.resolveForCLI("never_created1");
+ fail("Exception should occur before!");
+ } catch (CmdLineException e) {
+ assertEquals("No such job \u2018never_created1\u2019 exists. Perhaps you meant \u2018never_created\u2019?", e.getMessage());
+ }
+
+ }
+
+ public static class MockBuildTriggerThrowsNPEOnStart extends Trigger- {
+ @Override
+ public void start(hudson.model.Item project, boolean newInstance) {
+ throw new NullPointerException();
+ }
+
+ @TestExtension("loadingWithNPEOnTriggerStart")
+ public static class DescriptorImpl extends TriggerDescriptor {
+
+ @Override
+ public boolean isApplicable(hudson.model.Item item) {
+ return false;
+ }
+ }
+
+ }
+
+ @Issue("SECURITY-617")
+ @Test
+ public void upstreamDownstreamExportApi() throws Exception {
+ FreeStyleProject us = j.createFreeStyleProject("upstream-project");
+ FreeStyleProject ds = j.createFreeStyleProject("downstream-project");
+ us.getPublishersList().add(new BuildTrigger(Collections.singleton(ds), Result.SUCCESS));
+ j.jenkins.rebuildDependencyGraph();
+ assertEquals(Collections.singletonList(ds), us.getDownstreamProjects());
+ assertEquals(Collections.singletonList(us), ds.getUpstreamProjects());
+ j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
+ j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().
+ grant(Jenkins.READ).everywhere().toEveryone().
+ grant(Item.READ).everywhere().to("alice").
+ grant(Item.READ).onItems(us).to("bob").
+ grant(Item.READ).onItems(ds).to("charlie"));
+ String api = j.createWebClient().withBasicCredentials("alice").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString();
+ System.out.println(api);
+ assertThat(api, containsString("downstream-project"));
+ api = j.createWebClient().withBasicCredentials("alice").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString();
+ System.out.println(api);
+ assertThat(api, containsString("upstream-project"));
+ api = j.createWebClient().withBasicCredentials("bob").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString();
+ System.out.println(api);
+ assertThat(api, not(containsString("downstream-project")));
+ api = j.createWebClient().withBasicCredentials("charlie").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString();
+ System.out.println(api);
+ assertThat(api, not(containsString("upstream-project")));
+ }
+
+}
diff --git a/test/src/test/java/hudson/model/EnvironmentContributorTest.java b/test/src/test/java/hudson/model/EnvironmentContributorTest.java
new file mode 100644
index 0000000000..f7a99814e8
--- /dev/null
+++ b/test/src/test/java/hudson/model/EnvironmentContributorTest.java
@@ -0,0 +1,40 @@
+package hudson.model;
+
+import hudson.EnvVars;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.CaptureEnvironmentBuilder;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.TestExtension;
+
+public class EnvironmentContributorTest {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ /**
+ * Makes sure that the project-scoped environment variables are getting
+ * consulted.
+ */
+ @Test
+ public void projectScoped() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ CaptureEnvironmentBuilder c = new CaptureEnvironmentBuilder();
+ p.getBuildersList().add(c);
+ p.setDescription("Issac Newton");
+ j.assertBuildStatusSuccess(p.scheduleBuild2(0));
+
+ assertEquals("Issac Newton", c.getEnvVars().get("ABC"));
+ assertEquals("master", c.getEnvVars().get("NODE_NAME"));
+ }
+
+ @TestExtension("projectScoped")
+ public static class JobScopedInjection extends EnvironmentContributor {
+ @Override
+ public void buildEnvironmentFor(Job j, EnvVars envs, TaskListener listener) {
+ envs.put("ABC", j.getDescription());
+ }
+ }
+
+}
diff --git a/test/src/test/java/hudson/model/ExecutorTest.java b/test/src/test/java/hudson/model/ExecutorTest.java
index e13ff1c345..70254456fd 100644
--- a/test/src/test/java/hudson/model/ExecutorTest.java
+++ b/test/src/test/java/hudson/model/ExecutorTest.java
@@ -136,10 +136,16 @@ public class ExecutorTest {
grant(Jenkins.READ).everywhere().toEveryone().
grant(Item.READ).onItems(publicProject).toEveryone().
grant(Item.READ).onItems(secretProject).to("has-security-clearance"));
- String api = j.createWebClient().login("has-security-clearance").goTo(slave.toComputer().getUrl() + "api/json?pretty&depth=1", null).getWebResponse().getContentAsString();
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+ wc.withBasicCredentials("has-security-clearance");
+ String api = wc.goTo(slave.toComputer().getUrl() + "api/json?pretty&depth=1", null).getWebResponse().getContentAsString();
System.out.println(api);
assertThat(api, allOf(containsString("public-project"), containsString("secret-project")));
- api = j.createWebClient().login("regular-joe").goTo(slave.toComputer().getUrl() + "api/json?pretty&depth=1", null).getWebResponse().getContentAsString();
+
+ wc = j.createWebClient();
+ wc.withBasicCredentials("regular-joe");
+ api = wc.goTo(slave.toComputer().getUrl() + "api/json?pretty&depth=1", null).getWebResponse().getContentAsString();
System.out.println(api);
assertThat(api, allOf(containsString("public-project"), not(containsString("secret-project"))));
}
diff --git a/test/src/test/java/hudson/model/ItemsTest.java b/test/src/test/java/hudson/model/ItemsTest.java
index 32ec8e6e47..40dfd2fa8d 100644
--- a/test/src/test/java/hudson/model/ItemsTest.java
+++ b/test/src/test/java/hudson/model/ItemsTest.java
@@ -45,6 +45,7 @@ import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.apache.commons.httpclient.HttpStatus;
+import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.containsInAnyOrder;
@@ -123,6 +124,8 @@ public class ItemsTest {
// TODO would be more efficient to run these all as a single test case, but after a few Jetty seems to stop serving new content and new requests just hang.
private void overwriteTargetSetUp() throws Exception {
+ User.getById("attacker", true);
+
// A fully visible item:
r.createFreeStyleProject("visible").setDescription("visible");
// An item known to exist but not visible:
@@ -185,7 +188,7 @@ public class ItemsTest {
JenkinsRule.WebClient wc = wc(r);
wc.getOptions().setRedirectEnabled(false);
wc.getOptions().setThrowExceptionOnFailingStatusCode(false); // redirect perversely counts as a failure
- WebResponse webResponse = wc.getPage(new WebRequest(createCrumbedUrl(r, wc, "createItem?name=" + target + "&mode=hudson.model.FreeStyleProject"), HttpMethod.POST)).getWebResponse();
+ WebResponse webResponse = wc.getPage(new WebRequest(new URL(wc.getContextPath() + "createItem?name=" + target + "&mode=hudson.model.FreeStyleProject"), HttpMethod.POST)).getWebResponse();
if (webResponse.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
throw new FailingHttpStatusCodeException(webResponse);
}
@@ -198,7 +201,7 @@ public class ItemsTest {
JenkinsRule.WebClient wc = wc(r);
wc.getOptions().setRedirectEnabled(false);
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
- WebResponse webResponse = wc.getPage(new WebRequest(createCrumbedUrl(r, wc, "createItem?name=" + target + "&mode=copy&from=dupe"), HttpMethod.POST)).getWebResponse();
+ WebResponse webResponse = wc.getPage(new WebRequest(new URL(wc.getContextPath() + "createItem?name=" + target + "&mode=copy&from=dupe"), HttpMethod.POST)).getWebResponse();
r.jenkins.getItem("dupe").delete();
if (webResponse.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
throw new FailingHttpStatusCodeException(webResponse);
@@ -209,7 +212,7 @@ public class ItemsTest {
REST_CREATE {
@Override void run(JenkinsRule r, String target) throws Exception {
JenkinsRule.WebClient wc = wc(r);
- WebRequest req = new WebRequest(createCrumbedUrl(r, wc, "createItem?name=" + target), HttpMethod.POST);
+ WebRequest req = new WebRequest(new URL(wc.getContextPath() + "createItem?name=" + target), HttpMethod.POST);
req.setAdditionalHeader("Content-Type", "application/xml");
req.setRequestBody("
");
wc.getPage(req);
@@ -222,7 +225,7 @@ public class ItemsTest {
JenkinsRule.WebClient wc = wc(r);
wc.getOptions().setRedirectEnabled(false);
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
- WebResponse webResponse = wc.getPage(new WebRequest(createCrumbedUrl(r, wc, "job/dupe/doRename?newName=" + target), HttpMethod.POST)).getWebResponse();
+ WebResponse webResponse = wc.getPage(new WebRequest(new URL(wc.getContextPath() + "job/dupe/doRename?newName=" + target), HttpMethod.POST)).getWebResponse();
if (webResponse.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
r.jenkins.getItem("dupe").delete();
throw new FailingHttpStatusCodeException(webResponse);
@@ -275,14 +278,7 @@ public class ItemsTest {
};
abstract void run(JenkinsRule r, String target) throws Exception;
private static final JenkinsRule.WebClient wc(JenkinsRule r) throws Exception {
- return r.createWebClient().login("attacker");
- }
- // TODO replace with standard version once it is fixed to detect an existing query string
- private static URL createCrumbedUrl(JenkinsRule r, JenkinsRule.WebClient wc, String relativePath) throws IOException {
- CrumbIssuer issuer = r.jenkins.getCrumbIssuer();
- String crumbName = issuer.getDescriptor().getCrumbRequestField();
- String crumb = issuer.getCrumb(null);
- return new URL(wc.getContextPath() + relativePath + (relativePath.contains("?") ? "&" : "?") + crumbName + "=" + crumb);
+ return r.createWebClient().withBasicApiToken("attacker");
}
}
diff --git a/test/src/test/java/hudson/model/JobTest.java b/test/src/test/java/hudson/model/JobTest.java
index d20b9c28a6..58e14cdd33 100644
--- a/test/src/test/java/hudson/model/JobTest.java
+++ b/test/src/test/java/hudson/model/JobTest.java
@@ -202,7 +202,7 @@ public class JobTest {
JenkinsRule.WebClient wc = j.createWebClient();
wc.assertFails("job/testJob/", HttpURLConnection.HTTP_NOT_FOUND);
wc.assertFails("jobCaseInsensitive/testJob/", HttpURLConnection.HTTP_NOT_FOUND);
- wc.login("joe"); // Has Item.READ permission
+ wc.withBasicCredentials("joe"); // Has Item.READ permission
// Verify we can access both URLs:
wc.goTo("job/testJob/");
wc.goTo("jobCaseInsensitive/TESTJOB/");
@@ -216,11 +216,14 @@ public class JobTest {
Item.EXTENDED_READ.setEnabled(true);
try {
wc.assertFails("job/testJob/config.xml", HttpURLConnection.HTTP_FORBIDDEN);
- wc.login("alice"); // Has CONFIGURE and EXTENDED_READ permission
+
+ wc.withBasicApiToken(User.getById("alice", true)); // Has CONFIGURE and EXTENDED_READ permission
tryConfigDotXml(wc, 500, "Both perms; should get 500");
- wc.login("bob"); // Has only CONFIGURE permission (this should imply EXTENDED_READ)
+
+ wc.withBasicApiToken(User.getById("bob", true)); // Has only CONFIGURE permission (this should imply EXTENDED_READ)
tryConfigDotXml(wc, 500, "Config perm should imply EXTENDED_READ");
- wc.login("charlie"); // Has only EXTENDED_READ permission
+
+ wc.withBasicApiToken(User.getById("charlie", true)); // Has only EXTENDED_READ permission
tryConfigDotXml(wc, 403, "No permission, should get 403");
} finally {
Item.EXTENDED_READ.setEnabled(saveEnabled);
diff --git a/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java b/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java
index 2129c9fce9..4fab18d601 100644
--- a/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java
+++ b/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java
@@ -62,16 +62,21 @@ public class PasswordParameterDefinitionTest {
return true;
}
});
+
+ User admin = User.getById("admin", true);
+ User dev = User.getById("dev", true);
+
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false); // ParametersDefinitionProperty/index.jelly sends a 405 but really it is OK
// Control case: admin can use default value.
- j.submit(wc.login("admin").getPage(p, "build?delay=0sec").getFormByName("parameters"));
+ j.submit(wc.withBasicApiToken(admin).getPage(p, "build?delay=0sec").getFormByName("parameters"));
j.waitUntilNoActivity();
FreeStyleBuild b1 = p.getLastBuild();
assertEquals(1, b1.getNumber());
j.assertLogContains("I heard about a s3cr3t!", j.assertBuildStatusSuccess(b1));
+
// Another control case: anyone can enter a different value.
- HtmlForm form = wc.login("dev").getPage(p, "build?delay=0sec").getFormByName("parameters");
+ HtmlForm form = wc.withBasicApiToken(dev).getPage(p, "build?delay=0sec").getFormByName("parameters");
HtmlPasswordInput input = form.getInputByName("value");
input.setText("rumor");
j.submit(form);
@@ -79,14 +84,16 @@ public class PasswordParameterDefinitionTest {
FreeStyleBuild b2 = p.getLastBuild();
assertEquals(2, b2.getNumber());
j.assertLogContains("I heard about a rumor!", j.assertBuildStatusSuccess(b2));
+
// Test case: anyone can use default value.
- j.submit(wc.login("dev").getPage(p, "build?delay=0sec").getFormByName("parameters"));
+ j.submit(wc.withBasicApiToken(dev).getPage(p, "build?delay=0sec").getFormByName("parameters"));
j.waitUntilNoActivity();
FreeStyleBuild b3 = p.getLastBuild();
assertEquals(3, b3.getNumber());
j.assertLogContains("I heard about a s3cr3t!", j.assertBuildStatusSuccess(b3));
+
// Another control case: blank values.
- form = wc.login("dev").getPage(p, "build?delay=0sec").getFormByName("parameters");
+ form = wc.withBasicApiToken(dev).getPage(p, "build?delay=0sec").getFormByName("parameters");
input = form.getInputByName("value");
input.setText("");
j.submit(form);
diff --git a/test/src/test/java/hudson/model/ProjectTest.java b/test/src/test/java/hudson/model/ProjectTest.java
index a2d5bf3f67..f966f4e56b 100644
--- a/test/src/test/java/hudson/model/ProjectTest.java
+++ b/test/src/test/java/hudson/model/ProjectTest.java
@@ -32,9 +32,12 @@ import hudson.tasks.*;
import hudson.security.HudsonPrivateSecurityRealm;
import hudson.security.GlobalMatrixAuthorizationStrategy;
+import java.io.Closeable;
+import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
+import hudson.util.Scrambler;
import org.jvnet.hudson.reactor.ReactorException;
import org.jvnet.hudson.test.FakeChangeLogSCM;
import hudson.scm.SCMRevisionState;
@@ -553,9 +556,8 @@ public class ProjectTest {
GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy();
j.jenkins.setAuthorizationStrategy(auth);
j.jenkins.setCrumbIssuer(null);
- HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false);
- j.jenkins.setSecurityRealm(realm);
- User user = realm.createAccount("John Smith", "password");
+ j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
+ User user = User.getById("john", true);
try (ACLContext as = ACL.as(user)) {
project.doDoDelete(null, null);
fail("User should not have permission to build project");
@@ -568,7 +570,13 @@ public class ProjectTest {
auth.add(Jenkins.READ, user.getId());
auth.add(Job.READ, user.getId());
auth.add(Job.DELETE, user.getId());
- List forms = j.createWebClient().login(user.getId(), "password").goTo(project.getUrl() + "delete").getForms();
+
+ // use Basic to speedup the test, normally it's pure UI testing
+ JenkinsRule.WebClient wc = j.createWebClient();
+ wc.withBasicCredentials(user.getId());
+ HtmlPage p = wc.goTo(project.getUrl() + "delete");
+
+ List forms = p.getForms();
for(HtmlForm form:forms){
if("doDelete".equals(form.getAttribute("action"))){
j.submit(form);
@@ -605,9 +613,13 @@ public class ProjectTest {
String cmd = "echo hello > change.log";
project.getBuildersList().add(Functions.isWindows()? new BatchFile(cmd) : new Shell(cmd));
j.buildAndAssertSuccess(project);
- JenkinsRule.WebClient wc = j.createWebClient().login(user.getId(), "password");
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+ wc.withBasicCredentials(user.getId(), "password");
WebRequest request = new WebRequest(new URL(wc.getContextPath() + project.getUrl() + "doWipeOutWorkspace"), HttpMethod.POST);
HtmlPage p = wc.getPage(request);
+ assertEquals(p.getWebResponse().getStatusCode(), 200);
+
Thread.sleep(500);
assertFalse("Workspace should not exist.", project.getSomeWorkspace().exists());
}
@@ -633,7 +645,12 @@ public class ProjectTest {
auth.add(Job.READ, user.getId());
auth.add(Job.CONFIGURE, user.getId());
auth.add(Jenkins.READ, user.getId());
- List forms = j.createWebClient().login(user.getId(), "password").goTo(project.getUrl()).getForms();
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+ wc.withBasicCredentials(user.getId(), "password");
+ HtmlPage p = wc.goTo(project.getUrl());
+
+ List forms = p.getForms();
for(HtmlForm form:forms){
if("disable".equals(form.getAttribute("action"))){
j.submit(form);
@@ -666,7 +683,12 @@ public class ProjectTest {
auth.add(Job.READ, user.getId());
auth.add(Job.CONFIGURE, user.getId());
auth.add(Jenkins.READ, user.getId());
- List forms = j.createWebClient().login(user.getId(), "password").goTo(project.getUrl()).getForms();
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+ wc.withBasicCredentials(user.getId(), "password");
+ HtmlPage p = wc.goTo(project.getUrl());
+
+ List forms = p.getForms();
for(HtmlForm form:forms){
if("enable".equals(form.getAttribute("action"))){
j.submit(form);
diff --git a/test/src/test/java/hudson/model/QueueTest.java b/test/src/test/java/hudson/model/QueueTest.java
index 5e626aea3c..689b63e5ae 100644
--- a/test/src/test/java/hudson/model/QueueTest.java
+++ b/test/src/test/java/hudson/model/QueueTest.java
@@ -943,8 +943,12 @@ public class QueueTest {
project.getBuildersList().add(new SleepBuilder(10));
project.scheduleBuild2(0);
+ User alice = User.getById("alice", true);
+ User bob = User.getById("bob", true);
+ User james = User.getById("james", true);
+
JenkinsRule.WebClient webClient = r.createWebClient();
- webClient.login("bob", "bob");
+ webClient.withBasicApiToken(bob);
XmlPage p = webClient.goToXml("queue/api/xml");
//bob has permission on the project and will be able to see it in the queue together with information such as the URL and the name.
@@ -959,13 +963,15 @@ public class QueueTest {
}
}
}
+
webClient = r.createWebClient();
- webClient.login("alice");
+ webClient.withBasicApiToken(alice);
XmlPage p2 = webClient.goToXml("queue/api/xml");
//alice does not have permission on the project and will not see it in the queue.
assertTrue(p2.getByXPath("/queue/node()").isEmpty());
+
webClient = r.createWebClient();
- webClient.login("james");
+ webClient.withBasicApiToken(james);
XmlPage p3 = webClient.goToXml("queue/api/xml");
//james has DISCOVER permission on the project and will only be able to see the task name.
@@ -975,9 +981,9 @@ public class QueueTest {
// Also check individual item exports.
String url = project.getQueueItem().getUrl() + "api/xml";
- r.createWebClient().login("bob").goToXml(url); // OK, 200
- r.createWebClient().login("james").assertFails(url, HttpURLConnection.HTTP_FORBIDDEN); // only DISCOVER → AccessDeniedException
- r.createWebClient().login("alice").assertFails(url, HttpURLConnection.HTTP_NOT_FOUND); // not even DISCOVER
+ r.createWebClient().withBasicApiToken(bob).goToXml(url); // OK, 200
+ r.createWebClient().withBasicApiToken(james).assertFails(url, HttpURLConnection.HTTP_FORBIDDEN); // only DISCOVER → AccessDeniedException
+ r.createWebClient().withBasicApiToken(alice).assertFails(url, HttpURLConnection.HTTP_NOT_FOUND); // not even DISCOVER
}
//we force the project not to be executed so that it stays in the queue
diff --git a/test/src/test/java/hudson/model/Slave2Test.java b/test/src/test/java/hudson/model/SlaveTest.java
similarity index 69%
rename from test/src/test/java/hudson/model/Slave2Test.java
rename to test/src/test/java/hudson/model/SlaveTest.java
index 6ab40e3eed..76823820c3 100644
--- a/test/src/test/java/hudson/model/Slave2Test.java
+++ b/test/src/test/java/hudson/model/SlaveTest.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
- * Copyright (c) 2015 CloudBees, Inc.
+ * Copyright (c) 2004-2009, Sun Microsystems, 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
@@ -23,59 +23,117 @@
*/
package hudson.model;
+import com.gargoylesoftware.htmlunit.Page;
+import groovy.util.XmlSlurper;
import hudson.DescriptorExtensionList;
import hudson.ExtensionList;
+import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.DumbSlave;
+import hudson.slaves.JNLPLauncher;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import hudson.slaves.RetentionStrategy;
+import hudson.util.FormValidation;
+import static hudson.util.FormValidation.Kind.WARNING;
+import java.net.HttpURLConnection;
import java.net.MalformedURLException;
+import java.net.URL;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
-import org.junit.Rule;
-import org.junit.Test;
-import org.jvnet.hudson.test.JenkinsRule;
-
+import org.apache.commons.io.IOUtils;
import static org.hamcrest.Matchers.anyOf;
-import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasItem;
-import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeThat;
-
+import org.junit.Rule;
+import org.junit.Test;
import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
-/**
- * Tests for the {@link Slave} class.
- * There is also a Groovy implementation of such test file, hence the class name
- * has an index.
- * @author Oleg Nenashev
- */
-public class Slave2Test {
-
+public class SlaveTest {
+
@Rule
- public JenkinsRule rule = new JenkinsRule();
-
+ public JenkinsRule j = new JenkinsRule();
+
+ /**
+ * Makes sure that a form validation method gets inherited.
+ */
+ @Test
+ public void formValidation() throws Exception {
+ j.executeOnServer(() -> {
+ assertNotNull(j.jenkins.getDescriptor(DumbSlave.class).getCheckUrl("remoteFS"));
+ return null;
+ });
+ }
+
+ /**
+ * Programmatic config.xml submission.
+ */
+ @Test
+ public void slaveConfigDotXml() throws Exception {
+ DumbSlave s = j.createSlave();
+ JenkinsRule.WebClient wc = j.createWebClient();
+ Page p = wc.goTo("computer/" + s.getNodeName() + "/config.xml", "application/xml");
+ String xml = p.getWebResponse().getContentAsString();
+ new XmlSlurper().parseText(xml); // verify that it is XML
+
+ // make sure it survives the roundtrip
+ post("computer/" + s.getNodeName() + "/config.xml", xml);
+
+ assertNotNull(j.jenkins.getNode(s.getNodeName()));
+
+ xml = IOUtils.toString(getClass().getResource("SlaveTest/slave.xml").openStream());
+ xml = xml.replace("NAME", s.getNodeName());
+ post("computer/" + s.getNodeName() + "/config.xml", xml);
+
+ s = (DumbSlave) j.jenkins.getNode(s.getNodeName());
+ assertNotNull(s);
+ assertEquals("some text", s.getNodeDescription());
+ assertEquals(JNLPLauncher.class, s.getLauncher().getClass());
+ }
+
+ private void post(String url, String xml) throws Exception {
+ HttpURLConnection con = (HttpURLConnection) new URL(j.getURL(), url).openConnection();
+ con.setRequestMethod("POST");
+ con.setRequestProperty("Content-Type", "application/xml;charset=UTF-8");
+ con.setRequestProperty(CrumbIssuer.DEFAULT_CRUMB_NAME, "test");
+ con.setDoOutput(true);
+ con.getOutputStream().write(xml.getBytes("UTF-8"));
+ con.getOutputStream().close();
+ IOUtils.copy(con.getInputStream(), System.out);
+ }
+
+ @Test
+ public void remoteFsCheck() throws Exception {
+ DumbSlave.DescriptorImpl d = j.jenkins.getDescriptorByType(DumbSlave.DescriptorImpl.class);
+ assertEquals(FormValidation.ok(), d.doCheckRemoteFS("c:\\"));
+ assertEquals(FormValidation.ok(), d.doCheckRemoteFS("/tmp"));
+ assertEquals(WARNING, d.doCheckRemoteFS("relative/path").kind);
+ assertEquals(WARNING, d.doCheckRemoteFS("/net/foo/bar/zot").kind);
+ assertEquals(WARNING, d.doCheckRemoteFS("\\\\machine\\folder\\foo").kind);
+ }
+
@Test
@Issue("SECURITY-195")
public void shouldNotEscapeJnlpSlavesResources() throws Exception {
- Slave slave = rule.createSlave();
-
+ Slave slave = j.createSlave();
+
// Spot-check correct requests
assertJnlpJarUrlIsAllowed(slave, "agent.jar");
assertJnlpJarUrlIsAllowed(slave, "slave.jar");
assertJnlpJarUrlIsAllowed(slave, "remoting.jar");
assertJnlpJarUrlIsAllowed(slave, "jenkins-cli.jar");
assertJnlpJarUrlIsAllowed(slave, "hudson-cli.jar");
-
+
// Check that requests to other WEB-INF contents fail
assertJnlpJarUrlFails(slave, "web.xml");
assertJnlpJarUrlFails(slave, "web.xml");
@@ -83,7 +141,7 @@ public class Slave2Test {
assertJnlpJarUrlFails(slave, "classes/dependencies.txt");
assertJnlpJarUrlFails(slave, "plugins/ant.hpi");
assertJnlpJarUrlFails(slave, "nonexistentfolder/something.txt");
-
+
// Try various kinds of folder escaping (SECURITY-195)
assertJnlpJarUrlFails(slave, "../");
assertJnlpJarUrlFails(slave, "..");
@@ -93,7 +151,7 @@ public class Slave2Test {
assertJnlpJarUrlFails(slave, "foo/../../bar");
assertJnlpJarUrlFails(slave, "./../foo/bar");
}
-
+
private void assertJnlpJarUrlFails(@Nonnull Slave slave, @Nonnull String url) throws Exception {
// Raw access to API
Slave.JnlpJar jnlpJar = slave.getComputer().getJnlpJars(url);
@@ -105,15 +163,15 @@ public class Slave2Test {
}
fail("Expected the MalformedURLException for " + url);
}
-
+
private void assertJnlpJarUrlIsAllowed(@Nonnull Slave slave, @Nonnull String url) throws Exception {
// Raw access to API
Slave.JnlpJar jnlpJar = slave.getComputer().getJnlpJars(url);
assertNotNull(jnlpJar.getURL());
-
-
+
+
// Access from a Web client
- JenkinsRule.WebClient client = rule.createWebClient();
+ JenkinsRule.WebClient client = j.createWebClient();
assertEquals(200, client.getPage(client.getContextPath() + "jnlpJars/" + URLEncoder.encode(url, "UTF-8")).getWebResponse().getStatusCode());
assertEquals(200, client.getPage(jnlpJar.getURL()).getWebResponse().getStatusCode());
}
@@ -122,9 +180,9 @@ public class Slave2Test {
@Issue("JENKINS-36280")
public void launcherFiltering() throws Exception {
DumbSlave.DescriptorImpl descriptor =
- rule.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class);
+ j.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class);
DescriptorExtensionList> descriptors =
- rule.getInstance().getDescriptorList(ComputerLauncher.class);
+ j.getInstance().getDescriptorList(ComputerLauncher.class);
assumeThat("we need at least two launchers to test this", descriptors.size(), not(anyOf(is(0), is(1))));
assertThat(descriptor.computerLauncherDescriptors(null), containsInAnyOrder(descriptors.toArray(new Descriptor[descriptors.size()])));
@@ -140,7 +198,7 @@ public class Slave2Test {
@Issue("JENKINS-36280")
public void retentionFiltering() throws Exception {
DumbSlave.DescriptorImpl descriptor =
- rule.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class);
+ j.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class);
DescriptorExtensionList, Descriptor>> descriptors = RetentionStrategy.all();
assumeThat("we need at least two retention strategies to test this", descriptors.size(), not(anyOf(is(0), is(1))));
assertThat(descriptor.retentionStrategyDescriptors(null), containsInAnyOrder(descriptors.toArray(new Descriptor[descriptors.size()])));
@@ -157,7 +215,7 @@ public class Slave2Test {
@Issue("JENKINS-36280")
public void propertyFiltering() throws Exception {
DumbSlave.DescriptorImpl descriptor =
- rule.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class);
+ j.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class);
DescriptorExtensionList, NodePropertyDescriptor> descriptors = NodeProperty.all();
assumeThat("we need at least two node properties to test this", descriptors.size(), not(anyOf(is(0), is(1))));
assertThat(descriptor.nodePropertyDescriptors(null), containsInAnyOrder(descriptors.toArray(new Descriptor[descriptors.size()])));
@@ -170,8 +228,6 @@ public class Slave2Test {
assertThat(descriptor.nodePropertyDescriptors(null), hasItem(victim));
}
-
-
@TestExtension
public static class DynamicFilter extends DescriptorVisibilityFilter {
@@ -191,4 +247,5 @@ public class Slave2Test {
return !descriptors.contains(descriptor);
}
}
+
}
diff --git a/test/src/test/java/hudson/model/UserTest.java b/test/src/test/java/hudson/model/UserTest.java
index b81105fe55..34bb26a1c9 100644
--- a/test/src/test/java/hudson/model/UserTest.java
+++ b/test/src/test/java/hudson/model/UserTest.java
@@ -415,7 +415,7 @@ public class UserTest {
auth.add(Jenkins.ADMINISTER, user.getId());
auth.add(Jenkins.READ, user2.getId());
SecurityContextHolder.getContext().setAuthentication(user.impersonate());
- HtmlForm form = j.createWebClient().login(user.getId(), "password").goTo(user2.getUrl() + "/configure").getFormByName("config");
+ HtmlForm form = j.createWebClient().withBasicCredentials(user.getId(), "password").goTo(user2.getUrl() + "/configure").getFormByName("config");
form.getInputByName("_.fullName").setValueAttribute("Alice Smith");
j.submit(form);
assertEquals("User should have full name Alice Smith.", "Alice Smith", user2.getFullName());
@@ -429,7 +429,7 @@ public class UserTest {
fail("AccessDeniedException should be thrown.");
}
}
- form = j.createWebClient().login(user2.getId(), "password").goTo(user2.getUrl() + "/configure").getFormByName("config");
+ form = j.createWebClient().withBasicCredentials(user2.getId(), "password").goTo(user2.getUrl() + "/configure").getFormByName("config");
form.getInputByName("_.fullName").setValueAttribute("John");
j.submit(form);
diff --git a/test/src/test/java/hudson/model/ViewTest.java b/test/src/test/java/hudson/model/ViewTest.java
index 7a70d87df2..21aa56fc38 100644
--- a/test/src/test/java/hudson/model/ViewTest.java
+++ b/test/src/test/java/hudson/model/ViewTest.java
@@ -529,7 +529,7 @@ public class ViewTest {
return null;
}
});
- JenkinsRule.WebClient wc = j.createWebClient().login("admin");
+ JenkinsRule.WebClient wc = j.createWebClient().withBasicCredentials("admin");
assertEquals("original ${rootURL}/checkJobName still supported", "
", wc.goTo("checkJobName?value=stuff").getWebResponse().getContentAsString());
assertEquals("but now possible on a view in a folder", "
", wc.goTo("job/d1/view/All/checkJobName?value=stuff").getWebResponse().getContentAsString());
}
diff --git a/test/src/test/java/hudson/security/ACLTest.java b/test/src/test/java/hudson/security/ACLTest.java
new file mode 100644
index 0000000000..c97f01198b
--- /dev/null
+++ b/test/src/test/java/hudson/security/ACLTest.java
@@ -0,0 +1,75 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2017 CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package hudson.security;
+
+import hudson.model.FreeStyleProject;
+import hudson.model.Item;
+import java.util.Collection;
+import java.util.Collections;
+import org.acegisecurity.Authentication;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+
+public class ACLTest {
+
+ @Rule
+ public JenkinsRule r = new JenkinsRule();
+
+ @Issue("JENKINS-20474")
+ @Test
+ public void bypassStrategyOnSystem() throws Exception {
+ FreeStyleProject p = r.createFreeStyleProject();
+ r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
+ r.jenkins.setAuthorizationStrategy(new DoNotBotherMe());
+ assertTrue(p.hasPermission(Item.CONFIGURE));
+ assertTrue(p.hasPermission(ACL.SYSTEM, Item.CONFIGURE));
+ p.checkPermission(Item.CONFIGURE);
+ p.checkAbortPermission();
+ assertEquals(Collections.singletonList(p), r.jenkins.getAllItems());
+ }
+
+ private static class DoNotBotherMe extends AuthorizationStrategy {
+
+ @Override
+ public ACL getRootACL() {
+ return new ACL() {
+ @Override
+ public boolean hasPermission(Authentication a, Permission permission) {
+ throw new AssertionError("should not have needed to check " + permission + " for " + a);
+ }
+ };
+ }
+
+ @Override
+ public Collection getGroups() {
+ return Collections.emptySet();
+ }
+
+ }
+
+}
diff --git a/test/src/test/java/hudson/security/CliAuthenticationTest.java b/test/src/test/java/hudson/security/CliAuthenticationTest.java
index bdddf14407..c02d085586 100644
--- a/test/src/test/java/hudson/security/CliAuthenticationTest.java
+++ b/test/src/test/java/hudson/security/CliAuthenticationTest.java
@@ -1,24 +1,34 @@
package hudson.security;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
+import hudson.ExtensionList;
import hudson.cli.CLI;
import hudson.cli.CLICommand;
import jenkins.model.Jenkins;
+import jenkins.security.SecurityListener;
+import jenkins.security.SpySecurityListener;
import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.BadCredentialsException;
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.userdetails.User;
+import org.acegisecurity.userdetails.UserDetails;
+import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.apache.commons.io.input.NullInputStream;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.For;
+import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
+import org.springframework.dao.DataAccessException;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
+import static org.junit.Assert.*;
+
/**
* @author Kohsuke Kawaguchi
*/
@@ -27,6 +37,14 @@ public class CliAuthenticationTest {
@Rule
public JenkinsRule j = new JenkinsRule();
+
+ private SpySecurityListener spySecurityListener;
+
+ @Before
+ public void prepareListeners(){
+ //TODO simplify using #3021 into ExtensionList.lookupSingleton(SpySecurityListener.class)
+ this.spySecurityListener = ExtensionList.lookup(SecurityListener.class).get(SpySecurityListenerImpl.class);
+ }
@Test
public void test() throws Exception {
@@ -40,6 +58,10 @@ public class CliAuthenticationTest {
assertEquals(0, command(args));
}
+ private void unsuccessfulCommand(String... args) throws Exception {
+ assertNotEquals(0, command(args));
+ }
+
private int command(String... args) throws Exception {
try (CLI cli = new CLI(j.getURL())) {
return cli.execute(args);
@@ -91,9 +113,86 @@ public class CliAuthenticationTest {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
successfulCommand("login","--username","abc","--password","abc");
+ spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(userDetails -> userDetails.getUsername().equals("abc"));
+ spySecurityListener.loggedInCalls.assertLastEventIsAndThenRemoveIt("abc");
+
successfulCommand("test"); // now we can run without an explicit credential
+ spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(userDetails -> userDetails.getUsername().equals("abc"));
+ spySecurityListener.loggedInCalls.assertNoNewEvents();
+
successfulCommand("logout");
+ spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(userDetails -> userDetails.getUsername().equals("abc"));
+ spySecurityListener.loggedOutCalls.assertLastEventIsAndThenRemoveIt("abc");
+
successfulCommand("anonymous"); // now we should run as anonymous
+ spySecurityListener.authenticatedCalls.assertNoNewEvents();
+ spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents();
+ spySecurityListener.loggedInCalls.assertNoNewEvents();
+ spySecurityListener.loggedOutCalls.assertNoNewEvents();
+ }
+
+ @Test
+ @Issue("JENKINS-27026")
+ public void loginAsALegitimateUserButUnknown() throws Exception {
+ j.jenkins.setSecurityRealm(new MockSecurityRealm());
+
+ String username = "alice";
+
+ unsuccessfulCommand("login","--username",username,"--password","badCredentials");
+ spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt(username);
+ spySecurityListener.failedToLogInCalls.assertNoNewEvents();
+ spySecurityListener.authenticatedCalls.assertNoNewEvents();
+ spySecurityListener.loggedInCalls.assertNoNewEvents();
+// spySecurityListener.failedToLogInCalls.assertLastEventIsAndThenRemoveIt(username);
+
+ unsuccessfulCommand("login","--username",username,"--password","usernameNotFound");
+ spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt(username);
+ spySecurityListener.failedToLogInCalls.assertNoNewEvents();
+ spySecurityListener.authenticatedCalls.assertNoNewEvents();
+ spySecurityListener.loggedInCalls.assertNoNewEvents();
+// spySecurityListener.failedToLogInCalls.assertLastEventIsAndThenRemoveIt(username);
+
+ unsuccessfulCommand("login","--username",username,"--password","mayOrMayNotExist");
+ spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt(username);
+ spySecurityListener.failedToLogInCalls.assertNoNewEvents();
+ spySecurityListener.authenticatedCalls.assertNoNewEvents();
+ spySecurityListener.loggedInCalls.assertNoNewEvents();
+// spySecurityListener.failedToLogInCalls.assertLastEventIsAndThenRemoveIt(username);
+
+ // in case of authentication that throw a DataAccessException (see impersonatingUserDetailsService)
+ // the CLI login command does not work as expected
+ unsuccessfulCommand("login","--username",username,"--password","dataAccess");
+ spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents();
+ spySecurityListener.failedToLogInCalls.assertNoNewEvents();
+ spySecurityListener.authenticatedCalls.assertNoNewEvents();
+ spySecurityListener.loggedInCalls.assertNoNewEvents();
+// spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt(username);
+// spySecurityListener.failedToLogInCalls.assertLastEventIsAndThenRemoveIt(username);
+ }
+
+ private static class MockSecurityRealm extends AbstractPasswordBasedSecurityRealm {
+ @Override protected UserDetails authenticate(String username, String password) throws AuthenticationException {
+ switch(password){
+ case "badCredentials": throw new BadCredentialsException("BadCredentials requested");
+ case "usernameNotFound": throw new UsernameNotFoundException("UsernameNotFound requested");
+ case "mayOrMayNotExist": throw new UserMayOrMayNotExistException("MayOrMayNotExist requested");
+ case "dataAccess": throw new DataAccessException("DataAccess requested"){};
+ default:
+ return new User(
+ username, password,
+ true, true, true,
+ new GrantedAuthority[]{SecurityRealm.AUTHENTICATED_AUTHORITY}
+ );
+ }
+ }
+
+ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ return null;
+ }
+
+ @Override public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException {
+ return null;
+ }
}
/**
@@ -112,4 +211,7 @@ public class CliAuthenticationTest {
assertTrue(out1.contains("Bad Credentials. Search the server log for"));
assertTrue(out2.contains("Bad Credentials. Search the server log for"));
}
+
+ @TestExtension
+ public static class SpySecurityListenerImpl extends SpySecurityListener {}
}
diff --git a/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java b/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java
index f989e1439a..04d3820e21 100644
--- a/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java
+++ b/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java
@@ -52,7 +52,9 @@ public class ExtendedReadPermissionTest {
GlobalMatrixAuthorizationStrategy gas = (GlobalMatrixAuthorizationStrategy)as;
assertTrue("Charlie should have extended read for this test", gas.hasExplicitPermission("charlie",Item.EXTENDED_READ));
- JenkinsRule.WebClient wc = r.createWebClient().login("charlie","charlie");
+ JenkinsRule.WebClient wc = r.createWebClient();
+ wc.withBasicCredentials("charlie");
+
HtmlPage page = wc.goTo("job/a/configure");
HtmlForm form = page.getFormByName("config");
HtmlButton saveButton = r.getButtonByCaption(form,"Save");
@@ -68,7 +70,9 @@ public class ExtendedReadPermissionTest {
GlobalMatrixAuthorizationStrategy gas = (GlobalMatrixAuthorizationStrategy)as;
assertFalse("Charlie should not have extended read for this test", gas.hasExplicitPermission("charlie",Item.EXTENDED_READ));
- JenkinsRule.WebClient wc = r.createWebClient().login("charlie","charlie");
+ JenkinsRule.WebClient wc = r.createWebClient();
+ wc.withBasicCredentials("charlie");
+
wc.assertFails("job/a/configure", HttpURLConnection.HTTP_FORBIDDEN);
}
@@ -81,7 +85,9 @@ public class ExtendedReadPermissionTest {
GlobalMatrixAuthorizationStrategy gas = (GlobalMatrixAuthorizationStrategy)as;
assertFalse("Bob should not have extended read for this test", gas.hasExplicitPermission("bob",Item.EXTENDED_READ));
- JenkinsRule.WebClient wc = r.createWebClient().login("bob","bob");
+ JenkinsRule.WebClient wc = r.createWebClient();
+ wc.withBasicCredentials("bob");
+
wc.assertFails("job/a/configure", HttpURLConnection.HTTP_FORBIDDEN);
}
diff --git a/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java b/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java
index 966b82c2ff..e58fb48d0b 100644
--- a/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java
+++ b/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java
@@ -25,33 +25,57 @@
package hudson.security;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.HttpMethod;
+import com.gargoylesoftware.htmlunit.WebRequest;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.util.NameValuePair;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
+import hudson.ExtensionList;
import hudson.model.User;
import hudson.remoting.Base64;
import static hudson.security.HudsonPrivateSecurityRealm.CLASSIC;
import static hudson.security.HudsonPrivateSecurityRealm.PASSWORD_ENCODER;
import hudson.security.pages.SignupPage;
import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
+
import jenkins.security.ApiTokenProperty;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.xml.HasXPath.hasXPath;
import static org.junit.Assert.*;
+
+import jenkins.security.SecurityListener;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.JenkinsRule.WebClient;
+import org.jvnet.hudson.test.TestExtension;
import org.jvnet.hudson.test.WithoutJenkins;
import org.jvnet.hudson.test.recipes.LocalData;
+import javax.annotation.Nonnull;
+
public class HudsonPrivateSecurityRealmTest {
@Rule
public JenkinsRule j = new JenkinsRule();
+ private SpySecurityListenerImpl spySecurityListener;
+
+ @Before
+ public void linkExtension() throws Exception {
+ spySecurityListener = ExtensionList.lookup(SecurityListener.class).get(SpySecurityListenerImpl.class);
+ }
+
/**
* Tests the data compatibility with Hudson before 1.283.
* Starting 1.283, passwords are now stored hashed.
@@ -67,6 +91,7 @@ public class HudsonPrivateSecurityRealmTest {
// verify the sanity that the password is really used
// this should fail
j.createWebClient().login("bob", "bob");
+ fail();
} catch (FailingHttpStatusCodeException e) {
assertEquals(401,e.getStatusCode());
}
@@ -259,4 +284,106 @@ public class HudsonPrivateSecurityRealmTest {
assertNull(User.get("unknown2",false, Collections.emptyMap()));
}
+ @Issue("JENKINS-48383")
+ @Test
+ public void selfRegistrationTriggerLoggedIn() throws Exception {
+ HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
+ j.jenkins.setSecurityRealm(securityRealm);
+ j.jenkins.setCrumbIssuer(null);
+
+ assertTrue(spySecurityListener.loggedInUsernames.isEmpty());
+
+ createFirstAccount("admin");
+ assertTrue(spySecurityListener.loggedInUsernames.get(0).equals("admin"));
+
+ createAccountByAdmin("alice");
+ // no new event in such case
+ assertTrue(spySecurityListener.loggedInUsernames.isEmpty());
+
+ selfRegistration("bob");
+ assertTrue(spySecurityListener.loggedInUsernames.get(0).equals("bob"));
+ }
+
+ private void createFirstAccount(String login) throws Exception {
+ assertNull(User.getById(login, false));
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+
+ HudsonPrivateSecurityRealm.SignupInfo info = new HudsonPrivateSecurityRealm.SignupInfo();
+ info.username = login;
+ info.password1 = login;
+ info.password2 = login;
+ info.fullname = StringUtils.capitalize(login);
+
+ WebRequest request = new WebRequest(new URL(wc.getContextPath() + "securityRealm/createFirstAccount"), HttpMethod.POST);
+ request.setRequestParameters(Arrays.asList(
+ new NameValuePair("username", login),
+ new NameValuePair("password1", login),
+ new NameValuePair("password2", login),
+ new NameValuePair("fullname", StringUtils.capitalize(login)),
+ new NameValuePair("email", login + "@" + login + ".com")
+ ));
+
+ HtmlPage p = wc.getPage(request);
+ assertEquals(200, p.getWebResponse().getStatusCode());
+ assertTrue(p.getDocumentElement().getElementsByAttribute("div", "class", "error").isEmpty());
+
+ assertNotNull(User.getById(login, false));
+ }
+
+ private void createAccountByAdmin(String login) throws Exception {
+ // user should not exist before
+ assertNull(User.getById(login, false));
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+ wc.login("admin");
+
+ spySecurityListener.loggedInUsernames.clear();
+
+ HtmlPage page = wc.goTo("securityRealm/addUser");
+ HtmlForm form = page.getForms().stream()
+ .filter(htmlForm -> htmlForm.getActionAttribute().endsWith("/securityRealm/createAccountByAdmin"))
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("Form must be present"));
+
+ form.getInputByName("username").setValueAttribute(login);
+ form.getInputByName("password1").setValueAttribute(login);
+ form.getInputByName("password2").setValueAttribute(login);
+ form.getInputByName("fullname").setValueAttribute(StringUtils.capitalize(login));
+ form.getInputByName("email").setValueAttribute(login + "@" + login + ".com");
+
+ HtmlPage p = j.submit(form);
+ assertEquals(200, p.getWebResponse().getStatusCode());
+ assertTrue(p.getDocumentElement().getElementsByAttribute("div", "class", "error").isEmpty());
+
+ assertNotNull(User.getById(login, false));
+ }
+
+ private void selfRegistration(String login) throws Exception {
+ // user should not exist before
+ assertNull(User.getById(login, false));
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+ SignupPage signup = new SignupPage(wc.goTo("signup"));
+ signup.enterUsername(login);
+ signup.enterPassword(login);
+ signup.enterFullName(StringUtils.capitalize(login));
+ signup.enterEmail(login + "@" + login + ".com");
+
+ HtmlPage p = signup.submit(j);
+ assertEquals(200, p.getWebResponse().getStatusCode());
+ assertTrue(p.getDocumentElement().getElementsByAttribute("div", "class", "error").isEmpty());
+
+ assertNotNull(User.getById(login, false));
+ }
+
+ @TestExtension
+ public static class SpySecurityListenerImpl extends SecurityListener {
+ private List loggedInUsernames = new ArrayList<>();
+
+ @Override
+ protected void loggedIn(@Nonnull String username) {
+ loggedInUsernames.add(username);
+ }
+ }
}
diff --git a/test/src/test/java/hudson/security/LoginTest.java b/test/src/test/java/hudson/security/LoginTest.java
index 86c1a0662c..91ef0e6e34 100644
--- a/test/src/test/java/hudson/security/LoginTest.java
+++ b/test/src/test/java/hudson/security/LoginTest.java
@@ -11,6 +11,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
+import hudson.model.User;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
@@ -56,7 +57,7 @@ public class LoginTest {
WebClient wc = j.createWebClient();
wc.assertFails("loginError", SC_UNAUTHORIZED);
// but not once the user logs in.
- verifyNotError(wc.login("alice"));
+ verifyNotError(wc.withBasicApiToken(User.getById("alice", true)));
}
private HtmlForm prepareLoginFormWithRememberMeChecked(WebClient wc) throws IOException, org.xml.sax.SAXException {
diff --git a/test/src/test/java/hudson/security/TokenBasedRememberMeServices2Test.java b/test/src/test/java/hudson/security/TokenBasedRememberMeServices2Test.java
new file mode 100644
index 0000000000..35bea92686
--- /dev/null
+++ b/test/src/test/java/hudson/security/TokenBasedRememberMeServices2Test.java
@@ -0,0 +1,130 @@
+package hudson.security;
+
+import com.gargoylesoftware.htmlunit.util.Cookie;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import static java.util.logging.Level.FINEST;
+import java.util.stream.Collectors;
+import jenkins.model.Jenkins;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.BadCredentialsException;
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+import org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices;
+import org.acegisecurity.userdetails.User;
+import org.acegisecurity.userdetails.UserDetails;
+import org.acegisecurity.userdetails.UsernameNotFoundException;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.LoggerRule;
+import org.springframework.dao.DataAccessException;
+
+public class TokenBasedRememberMeServices2Test {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ @Rule
+ public LoggerRule logging = new LoggerRule();
+
+ private static boolean failureInduced;
+
+ @Before
+ public void resetFailureInduced() {
+ failureInduced = false;
+ }
+
+ @Test
+ public void rememberMeAutoLoginFailure() throws Exception {
+ j.jenkins.setSecurityRealm(new InvalidUserWhenLoggingBackInRealm());
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+ wc.login("alice", "alice", true);
+
+ // we should see a remember me cookie
+ Cookie c = getRememberMeCookie(wc);
+ assertNotNull(c);
+
+ // start a new session and attempt to access Jenkins,
+ // which should cause autoLogin failures
+ wc = j.createWebClient();
+ wc.getCookieManager().addCookie(c);
+
+ // even if SecurityRealm chokes, it shouldn't kill the page
+ logging.capture(1000).record(TokenBasedRememberMeServices.class, FINEST);
+ wc.goTo("");
+
+ // make sure that the server recorded this failure
+ assertTrue(failureInduced);
+ assertTrue(logging.getMessages().stream().anyMatch(m -> m.contains("contained username 'alice' but was not found")));
+ // and the problematic cookie should have been removed
+ assertNull(getRememberMeCookie(wc));
+ }
+
+ private Cookie getRememberMeCookie(JenkinsRule.WebClient wc) {
+ return wc.getCookieManager().getCookie(TokenBasedRememberMeServices2.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
+ }
+
+ private static class InvalidUserWhenLoggingBackInRealm extends AbstractPasswordBasedSecurityRealm {
+ @Override
+ protected UserDetails authenticate(String username, String password) throws AuthenticationException {
+ if (username.equals(password)) {
+ return new User(username, password, true, new GrantedAuthority[] {new GrantedAuthorityImpl("myteam")});
+ }
+ throw new BadCredentialsException(username);
+ }
+
+ @Override
+ public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+ failureInduced = true;
+ throw new UsernameNotFoundException("intentionally not working");
+ }
+ }
+
+ @Test
+ public void basicFlow() throws Exception {
+ j.jenkins.setSecurityRealm(new StupidRealm());
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+ wc.login("bob", "bob", true);
+
+ // we should see a remember me cookie
+ Cookie c = getRememberMeCookie(wc);
+ assertNotNull(c);
+
+ // start a new session and attempt to access Jenkins,
+ wc = j.createWebClient();
+ wc.getCookieManager().addCookie(c);
+
+ // this will trigger remember me
+ wc.goTo("");
+
+ // make sure that our security realm failed to report the info correctly
+ assertTrue(failureInduced);
+ // but we should have logged in
+ wc.executeOnServer(() -> {
+ Authentication a = Jenkins.getAuthentication();
+ assertEquals("bob", a.getName());
+ assertEquals(ImmutableList.of("authenticated", "myteam"), Arrays.asList(a.getAuthorities()).stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
+ return null;
+ });
+ }
+
+ private static class StupidRealm extends InvalidUserWhenLoggingBackInRealm {
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+ failureInduced = true;
+ throw new UserMayOrMayNotExistException("I cannot tell");
+ }
+ }
+
+}
diff --git a/test/src/test/java/hudson/security/csrf/DefaultCrumbIssuerTest.java b/test/src/test/java/hudson/security/csrf/DefaultCrumbIssuerTest.java
index f3fddef1af..52dc455faf 100644
--- a/test/src/test/java/hudson/security/csrf/DefaultCrumbIssuerTest.java
+++ b/test/src/test/java/hudson/security/csrf/DefaultCrumbIssuerTest.java
@@ -8,6 +8,8 @@ package hudson.security.csrf;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import jenkins.model.Jenkins;
+import junit.framework.Assert;
import net.sf.json.JSONObject;
import org.junit.Before;
import org.junit.Rule;
@@ -135,4 +137,19 @@ public class DefaultCrumbIssuerTest {
wc.assertFails("crumbIssuer/api/json?jsonp=hack", HttpURLConnection.HTTP_FORBIDDEN);
}
+ @Issue("JENKINS-34254")
+ @Test public void testRequirePostErrorPageCrumb() throws Exception {
+ Jenkins.getInstance().setCrumbIssuer(new DefaultCrumbIssuer(false));
+ WebClient wc = r.createWebClient();
+ try {
+ wc.goTo("quietDown");
+ fail("expected failure");
+ } catch (FailingHttpStatusCodeException ex) {
+ Assert.assertEquals("expect HTTP 405 method not allowed", 405, ex.getStatusCode());
+ }
+ HtmlPage retry = (HtmlPage) wc.getCurrentWindow().getEnclosedPage();
+ HtmlPage success = r.submit(retry.getFormByName("retry"));
+ Assert.assertTrue("quieting down", r.jenkins.isQuietingDown());
+ }
+
}
diff --git a/test/src/test/java/hudson/util/RobustReflectionConverterTest.java b/test/src/test/java/hudson/util/RobustReflectionConverterTest.java
index 0bff3490cf..c57acd55cd 100644
--- a/test/src/test/java/hudson/util/RobustReflectionConverterTest.java
+++ b/test/src/test/java/hudson/util/RobustReflectionConverterTest.java
@@ -27,16 +27,18 @@ package hudson.util;
import hudson.cli.CLICommandInvoker;
import hudson.diagnosis.OldDataMonitor;
import hudson.model.AbstractDescribableImpl;
-import hudson.model.Items;
-import hudson.model.JobProperty;
-import hudson.model.JobPropertyDescriptor;
import hudson.model.Descriptor;
import hudson.model.FreeStyleProject;
+import hudson.model.Items;
import hudson.model.Job;
+import hudson.model.JobProperty;
+import hudson.model.JobPropertyDescriptor;
import hudson.model.Saveable;
+import hudson.model.User;
import hudson.security.ACL;
import java.io.ByteArrayInputStream;
+import java.net.URL;
import java.util.Collections;
import java.util.Map;
@@ -81,7 +83,7 @@ public class RobustReflectionConverterTest {
// GUI related implementations (@DataBoundConstructor and newInstance) aren't used actually
// (no jelly files are provides and they don't work actually),
// but written to clarify a use case.
- public static class AcceptOnlySpecificKeyword extends AbstractDescribableImpl{
+ public static class AcceptOnlySpecificKeyword extends AbstractDescribableImpl {
public static final String ACCEPT_KEYWORD = "accept";
private final String keyword;
@@ -185,7 +187,9 @@ public class RobustReflectionConverterTest {
@Test
public void testRestInterfaceFailure() throws Exception {
Items.XSTREAM2.addCriticalField(KeywordProperty.class, "criticalField");
-
+
+ User test = User.getById("test", true);
+
// without addCriticalField. This is accepted.
{
FreeStyleProject p = r.createFreeStyleProject();
@@ -198,11 +202,8 @@ public class RobustReflectionConverterTest {
// Configure a bad keyword via REST.
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
WebClient wc = r.createWebClient();
- wc.login("test", "test");
- WebRequest req = new WebRequest(
- wc.createCrumbedUrl(String.format("%s/config.xml", p.getUrl())),
- HttpMethod.POST
- );
+ wc.withBasicApiToken(test);
+ WebRequest req = new WebRequest(new URL(wc.getContextPath() + String.format("%s/config.xml", p.getUrl())), HttpMethod.POST);
req.setEncodingType(null);
req.setRequestBody(String.format(CONFIGURATION_TEMPLATE, "badvalue", AcceptOnlySpecificKeyword.ACCEPT_KEYWORD));
wc.getPage(req);
@@ -231,11 +232,8 @@ public class RobustReflectionConverterTest {
// Configure a bad keyword via REST.
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
WebClient wc = r.createWebClient();
- wc.login("test", "test");
- WebRequest req = new WebRequest(
- wc.createCrumbedUrl(String.format("%s/config.xml", p.getUrl())),
- HttpMethod.POST
- );
+ wc.withBasicApiToken(test);
+ WebRequest req = new WebRequest(new URL(wc.getContextPath() + String.format("%s/config.xml", p.getUrl())), HttpMethod.POST);
req.setEncodingType(null);
req.setRequestBody(String.format(CONFIGURATION_TEMPLATE, AcceptOnlySpecificKeyword.ACCEPT_KEYWORD, "badvalue"));
diff --git a/test/src/test/java/hudson/widgets/HistoryWidgetTest.java b/test/src/test/java/hudson/widgets/HistoryWidgetTest.java
new file mode 100644
index 0000000000..fd28e63f19
--- /dev/null
+++ b/test/src/test/java/hudson/widgets/HistoryWidgetTest.java
@@ -0,0 +1,27 @@
+package hudson.widgets;
+
+import hudson.model.FreeStyleProject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+
+public class HistoryWidgetTest {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ @Test
+ @Issue("JENKINS-15499")
+ public void moreLink() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ for (int x = 0; x < 3; x++) {
+ j.assertBuildStatusSuccess(p.scheduleBuild2(0));
+ }
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+ wc.setJavaScriptEnabled(false);
+ wc.goTo("job/" + p.getName() + "/buildHistory/all");
+ }
+
+}
diff --git a/test/src/test/java/jenkins/bugs/Jenkins19124Test.java b/test/src/test/java/jenkins/bugs/Jenkins19124Test.java
new file mode 100644
index 0000000000..4f976f85ba
--- /dev/null
+++ b/test/src/test/java/jenkins/bugs/Jenkins19124Test.java
@@ -0,0 +1,94 @@
+package jenkins.bugs;
+
+import com.gargoylesoftware.htmlunit.WebClientUtil;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlSelect;
+import com.gargoylesoftware.htmlunit.html.HtmlTextInput;
+import hudson.Launcher;
+import hudson.model.AbstractBuild;
+import hudson.model.AbstractProject;
+import hudson.model.BuildListener;
+import hudson.model.FreeStyleProject;
+import hudson.tasks.BuildStepDescriptor;
+import hudson.tasks.Builder;
+import hudson.util.FormValidation;
+import hudson.util.ListBoxModel;
+import java.io.IOException;
+import javax.inject.Inject;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.TestExtension;
+import org.kohsuke.stapler.QueryParameter;
+
+public class Jenkins19124Test {
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ @Inject
+ public Foo.DescriptorImpl d;
+
+ @Issue("JENKINS-19124")
+ @Test
+ public void interrelatedFormValidation() throws Exception {
+ j.jenkins.getInjector().injectMembers(this);
+
+ FreeStyleProject p = j.createFreeStyleProject();
+ p.getBuildersList().add(new Foo());
+
+ JenkinsRule.WebClient wc = j.createWebClient();
+ HtmlPage c = wc.getPage(p, "configure");
+ HtmlTextInput alpha = c.getElementByName("_.alpha");
+ alpha.setValueAttribute("hello");
+ WebClientUtil.waitForJSExec(wc);
+ assertEquals("hello", d.alpha);
+ assertEquals("2", d.bravo);
+
+ HtmlSelect bravo = c.getElementByName("_.bravo");
+ bravo.setSelectedAttribute("1", true);
+ WebClientUtil.waitForJSExec(wc);
+ assertEquals("hello", d.alpha);
+ assertEquals("1", d.bravo);
+ }
+
+ public static class Foo extends Builder {
+
+ public String getAlpha() {
+ return "alpha";
+ }
+ public String getBravo() {
+ return "2";
+ }
+
+ @Override
+ public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
+ return true;
+ }
+
+ @TestExtension
+ public static class DescriptorImpl extends BuildStepDescriptor {
+
+ String alpha, bravo;
+
+ public FormValidation doCheckAlpha(@QueryParameter String value, @QueryParameter String bravo) {
+ this.alpha = value;
+ this.bravo = bravo;
+ return FormValidation.ok();
+ }
+
+ public ListBoxModel doFillBravoItems() {
+ return new ListBoxModel().add("1").add("2").add("3");
+ }
+
+ @Override
+ public boolean isApplicable(Class extends AbstractProject> jobType) {
+ return true;
+ }
+
+ }
+
+ }
+
+}
diff --git a/test/src/test/groovy/jenkins/bugs/Jenkins41511Test.java b/test/src/test/java/jenkins/bugs/Jenkins41511Test.java
similarity index 100%
rename from test/src/test/groovy/jenkins/bugs/Jenkins41511Test.java
rename to test/src/test/java/jenkins/bugs/Jenkins41511Test.java
diff --git a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java
new file mode 100644
index 0000000000..856e5263ba
--- /dev/null
+++ b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java
@@ -0,0 +1,160 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2017 CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.install;
+
+import hudson.ClassicPluginStrategy;
+import hudson.ClassicPluginStrategy.DetachedPlugin;
+import hudson.Plugin;
+import hudson.PluginManager;
+import hudson.PluginManagerUtil;
+import hudson.PluginWrapper;
+import hudson.util.VersionNumber;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.RestartableJenkinsRule;
+import org.jvnet.hudson.test.recipes.LocalData;
+
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class LoadDetachedPluginsTest {
+
+ @Rule public RestartableJenkinsRule rr = PluginManagerUtil.newRestartableJenkinsRule();
+
+ @Issue("JENKINS-48365")
+ @Test
+ @LocalData
+ public void upgradeFromJenkins1() throws IOException {
+ VersionNumber since = new VersionNumber("1.550");
+ rr.then(r -> {
+ List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since);
+ assertThat("Plugins have been detached since the pre-upgrade version",
+ detachedPlugins.size(), greaterThan(4));
+ assertThat("Plugins detached between the pre-upgrade version and the current version should be installed",
+ getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size()));
+ assertNoFailedPlugins(r);
+ });
+ }
+
+ @Issue("JENKINS-48365")
+ @Test
+ @LocalData
+ public void upgradeFromJenkins2() {
+ VersionNumber since = new VersionNumber("2.0");
+ rr.then(r -> {
+ List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since);
+ assertThat("Plugins have been detached since the pre-upgrade version",
+ detachedPlugins.size(), greaterThan(1));
+ assertThat("Plugins detached between the pre-upgrade version and the current version should be installed",
+ getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size()));
+ assertNoFailedPlugins(r);
+ });
+ }
+
+ @Issue("JENKINS-48604")
+ @Test
+ @LocalData
+ public void upgradeFromJenkins2WithNewerDependency() {
+ VersionNumber since = new VersionNumber("2.0");
+ rr.then(r -> {
+ List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since);
+ assertThat("Plugins have been detached since the pre-upgrade version",
+ detachedPlugins.size(), greaterThan(1));
+ assertThat("Plugins detached between the pre-upgrade version and the current version should be installed",
+ getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size()));
+ Plugin scriptSecurity = r.jenkins.getPlugin("script-security");
+ assertThat("Script-security should be installed", scriptSecurity, notNullValue());
+ assertThat("Dependencies of detached plugins should not be downgraded",
+ scriptSecurity.getWrapper().getVersionNumber(), equalTo(new VersionNumber("1.34")));
+ assertNoFailedPlugins(r);
+ });
+ }
+
+ @Test
+ @LocalData
+ public void upgradeFromJenkins2WithOlderDependency() {
+ VersionNumber since = new VersionNumber("2.0");
+ rr.then(r -> {
+ List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since);
+ assertThat("Plugins have been detached since the pre-upgrade version",
+ detachedPlugins.size(), greaterThan(1));
+ assertThat("Plugins detached between the pre-upgrade version and the current version should be installed",
+ getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size()));
+ Plugin scriptSecurity = r.jenkins.getPlugin("script-security");
+ assertThat("Script-security should be installed", scriptSecurity, notNullValue());
+ assertThat("Dependencies of detached plugins should be upgraded to the required version",
+ scriptSecurity.getWrapper().getVersionNumber(), equalTo(new VersionNumber("1.18.1")));
+ assertNoFailedPlugins(r);
+ });
+ }
+
+ @Test
+ public void newInstallation() {
+ rr.then(r -> {
+ List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins();
+ assertThat("Detached plugins should exist", detachedPlugins, not(empty()));
+ assertThat("Detached plugins should not be installed on a new instance",
+ getInstalledDetachedPlugins(r, detachedPlugins), empty());
+ assertNoFailedPlugins(r);
+ });
+ rr.then(r -> {
+ List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins();
+ assertThat("Detached plugins should exist", detachedPlugins, not(empty()));
+ assertThat("Detached plugins should not be installed after restarting",
+ getInstalledDetachedPlugins(r, detachedPlugins), empty());
+ assertNoFailedPlugins(r);
+ });
+ }
+
+ private List getInstalledDetachedPlugins(JenkinsRule r, List detachedPlugins) {
+ PluginManager pluginManager = r.jenkins.getPluginManager();
+ List installedPlugins = new ArrayList<>();
+ for (DetachedPlugin plugin : detachedPlugins) {
+ PluginWrapper wrapper = pluginManager.getPlugin(plugin.getShortName());
+ if (wrapper != null) {
+ installedPlugins.add(wrapper);
+ assertTrue("Detached plugins should be active if installed", wrapper.isActive());
+ assertThat("Detached plugins should not have dependency errors", wrapper.getDependencyErrors(), empty());
+ }
+ }
+ return installedPlugins;
+ }
+
+ private void assertNoFailedPlugins(JenkinsRule r) {
+ assertThat("Detached plugins and their dependencies should not fail to install",
+ r.jenkins.getPluginManager().getFailedPlugins(), empty());
+ }
+
+}
diff --git a/test/src/test/java/jenkins/install/SetupWizardRestartTest.java b/test/src/test/java/jenkins/install/SetupWizardRestartTest.java
new file mode 100644
index 0000000000..474c1772f9
--- /dev/null
+++ b/test/src/test/java/jenkins/install/SetupWizardRestartTest.java
@@ -0,0 +1,50 @@
+package jenkins.install;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runners.model.Statement;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.RestartableJenkinsRule;
+
+import hudson.Main;
+import jenkins.model.Jenkins;
+
+public class SetupWizardRestartTest {
+ @Rule
+ public RestartableJenkinsRule rr = new RestartableJenkinsRule();
+
+ @Issue("JENKINS-47439")
+ @Test
+ public void restartKeepsSetupWizardState() {
+ rr.addStep(new Statement() {
+ @Override
+ public void evaluate() throws IOException {
+ // Modify state so that we get into the same conditions as a real start
+ Main.isUnitTest = false;
+ FileUtils.write(InstallUtil.getLastExecVersionFile(), "");
+ Jenkins j = rr.j.getInstance();
+ // Re-evaluate current state based on the new context
+ InstallUtil.proceedToNextStateFrom(InstallState.UNKNOWN);
+ assertEquals("Unexpected install state", InstallState.NEW, j.getInstallState());
+ assertTrue("Expecting setup wizard filter to be up", j.getSetupWizard().hasSetupWizardFilter());
+ InstallUtil.saveLastExecVersion();
+ }
+ });
+ // Check that the state is retained after a restart
+ rr.addStep(new Statement() {
+ @Override
+ public void evaluate() {
+ Jenkins j = rr.j.getInstance();
+ assertEquals("Unexpected install state", InstallState.NEW, j.getInstallState());
+ assertTrue("Expecting setup wizard filter to be up after restart", j.getSetupWizard().hasSetupWizardFilter());
+ }
+ });
+ }
+
+}
diff --git a/test/src/test/java/jenkins/model/JenkinsTest.java b/test/src/test/java/jenkins/model/JenkinsTest.java
index 2e488bd80b..a68ee0a4d6 100644
--- a/test/src/test/java/jenkins/model/JenkinsTest.java
+++ b/test/src/test/java/jenkins/model/JenkinsTest.java
@@ -314,20 +314,23 @@ public class JenkinsTest {
grant(Jenkins.READ).everywhere().to("bob").
grantWithoutImplication(Jenkins.ADMINISTER, Jenkins.READ).everywhere().to("charlie"));
WebClient wc = j.createWebClient();
- wc.login("alice");
+
+ wc.withBasicApiToken(User.getById("alice", true));
wc.goTo("script");
wc.assertFails("script?script=System.setProperty('hack','me')", HttpURLConnection.HTTP_BAD_METHOD);
assertNull(System.getProperty("hack"));
WebRequest req = new WebRequest(new URL(wc.getContextPath() + "script?script=System.setProperty('hack','me')"), HttpMethod.POST);
- wc.getPage(wc.addCrumb(req));
+ wc.getPage(req);
assertEquals("me", System.getProperty("hack"));
wc.assertFails("scriptText?script=System.setProperty('hack','me')", HttpURLConnection.HTTP_BAD_METHOD);
req = new WebRequest(new URL(wc.getContextPath() + "scriptText?script=System.setProperty('huck','you')"), HttpMethod.POST);
- wc.getPage(wc.addCrumb(req));
+ wc.getPage(req);
assertEquals("you", System.getProperty("huck"));
- wc.login("bob");
+
+ wc.withBasicApiToken(User.getById("bob", true));
wc.assertFails("script", HttpURLConnection.HTTP_FORBIDDEN);
- wc.login("charlie");
+
+ wc.withBasicApiToken(User.getById("charlie", true));
wc.assertFails("script", HttpURLConnection.HTTP_FORBIDDEN);
}
@@ -338,18 +341,22 @@ public class JenkinsTest {
grant(Jenkins.ADMINISTER).everywhere().to("alice").
grant(Jenkins.READ).everywhere().to("bob").
grantWithoutImplication(Jenkins.ADMINISTER, Jenkins.READ).everywhere().to("charlie"));
+
WebClient wc = j.createWebClient();
- wc.login("alice");
+
+ wc.withBasicApiToken(User.getById("alice", true));
wc.assertFails("eval", HttpURLConnection.HTTP_BAD_METHOD);
assertEquals("3", eval(wc));
- wc.login("bob");
+
+ wc.withBasicApiToken(User.getById("bob", true));
try {
eval(wc);
fail("bob has only READ");
} catch (FailingHttpStatusCodeException e) {
assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getStatusCode());
}
- wc.login("charlie");
+
+ wc.withBasicApiToken(User.getById("charlie", true));
try {
eval(wc);
fail("charlie has ADMINISTER but not RUN_SCRIPTS");
@@ -358,7 +365,7 @@ public class JenkinsTest {
}
}
private String eval(WebClient wc) throws Exception {
- WebRequest req = new WebRequest(wc.createCrumbedUrl("eval"), HttpMethod.POST);
+ WebRequest req = new WebRequest(new URL(wc.getContextPath() + "eval"), HttpMethod.POST);
req.setEncodingType(null);
req.setRequestBody("${1+2} ");
return wc.getPage(req).getWebResponse().getContentAsString();
diff --git a/test/src/test/java/jenkins/model/PeepholePermalinkTest.java b/test/src/test/java/jenkins/model/PeepholePermalinkTest.java
new file mode 100644
index 0000000000..c25e276b4e
--- /dev/null
+++ b/test/src/test/java/jenkins/model/PeepholePermalinkTest.java
@@ -0,0 +1,99 @@
+package jenkins.model;
+
+import hudson.Functions;
+import hudson.Util;
+import hudson.model.FreeStyleBuild;
+import hudson.model.FreeStyleProject;
+import hudson.model.Run;
+import java.io.File;
+import static org.junit.Assert.*;
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.FailureBuilder;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+
+public class PeepholePermalinkTest {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ /**
+ * Basic operation of the permalink generation.
+ */
+ @Test
+ public void basics() throws Exception {
+ Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows());
+
+ FreeStyleProject p = j.createFreeStyleProject();
+ FreeStyleBuild b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
+
+ File lsb = new File(p.getBuildDir(), "lastSuccessfulBuild");
+ File lfb = new File(p.getBuildDir(), "lastFailedBuild");
+
+ assertLink(lsb, b1);
+
+ // now another build that fails
+ p.getBuildersList().add(new FailureBuilder());
+ FreeStyleBuild b2 = p.scheduleBuild2(0).get();
+
+ assertLink(lsb, b1);
+ assertLink(lfb, b2);
+
+ // one more build and this time it succeeds
+ p.getBuildersList().clear();
+ FreeStyleBuild b3 = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
+
+ assertLink(lsb, b3);
+ assertLink(lfb, b2);
+
+ // delete b3 and symlinks should update properly
+ b3.delete();
+ assertLink(lsb, b1);
+ assertLink(lfb, b2);
+
+ b1.delete();
+ assertLink(lsb, null);
+ assertLink(lfb, b2);
+
+ b2.delete();
+ assertLink(lsb, null);
+ assertLink(lfb, null);
+ }
+
+ private void assertLink(File symlink, Run build) throws Exception {
+ assertEquals(build == null ? "-1" : Integer.toString(build.getNumber()), Util.resolveSymlink(symlink));
+ }
+
+ /**
+ * job/JOBNAME/lastStable and job/JOBNAME/lastSuccessful symlinks that we
+ * used to generate should still work
+ */
+ @Test
+ public void legacyCompatibility() throws Exception {
+ Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows());
+
+ FreeStyleProject p = j.createFreeStyleProject();
+ FreeStyleBuild b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
+
+ for (String n : new String[] {"lastStable", "lastSuccessful"}) {
+ // test if they both point to b1
+ assertEquals(new File(p.getRootDir(), n + "/build.xml").length(), new File(b1.getRootDir(), "build.xml").length());
+ }
+ }
+
+ @Test
+ @Issue("JENKINS-19034")
+ public void rebuildBuildNumberPermalinks() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject();
+ FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
+ File f = new File(p.getBuildDir(), "1");
+ // assertTrue(Util.isSymlink(f))
+ f.delete();
+ PeepholePermalink link = (PeepholePermalink) p.getPermalinks().stream().filter(l -> l instanceof PeepholePermalink).findAny().get();
+ link.updateCache(p, b);
+ assertTrue("build symlink hasn't been restored", f.exists());
+ }
+
+}
diff --git a/test/src/test/java/jenkins/security/ApiCrumbExclusionTest.java b/test/src/test/java/jenkins/security/ApiCrumbExclusionTest.java
new file mode 100644
index 0000000000..eef3b21dfd
--- /dev/null
+++ b/test/src/test/java/jenkins/security/ApiCrumbExclusionTest.java
@@ -0,0 +1,146 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2017 CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package jenkins.security;
+
+import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.HttpMethod;
+import com.gargoylesoftware.htmlunit.Page;
+import com.gargoylesoftware.htmlunit.WebRequest;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlFormUtil;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import hudson.model.UnprotectedRootAction;
+import hudson.model.User;
+import hudson.security.csrf.DefaultCrumbIssuer;
+import hudson.util.HttpResponses;
+import hudson.util.Scrambler;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.JenkinsRule.WebClient;
+import org.jvnet.hudson.test.TestExtension;
+import org.kohsuke.stapler.HttpResponse;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.net.URL;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class ApiCrumbExclusionTest {
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ private WebClient wc;
+
+ @Test
+ @Issue("JENKINS-22474")
+ public void callUsingApiTokenDoesNotRequireCSRFToken() throws Exception {
+ j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
+ j.jenkins.setCrumbIssuer(null);
+ User foo = User.get("foo");
+
+ wc = j.createWebClient();
+
+ // API Token
+ wc.withBasicApiToken(foo);
+ makeRequestAndVerify("foo");
+
+ // Basic auth using password
+ wc = j.createWebClient();
+ wc.withBasicCredentials("foo");
+ makeRequestAndVerify("foo");
+
+ wc = j.createWebClient();
+ wc.login("foo");
+ checkWeCanChangeMyDescription(200);
+
+ wc = j.createWebClient();
+ j.jenkins.setCrumbIssuer(new DefaultCrumbIssuer(false));
+
+ // even with crumbIssuer enabled, we are not required to send a CSRF token when using API token
+ wc.withBasicApiToken(foo);
+ makeRequestAndVerify("foo");
+
+ // Basic auth using password requires crumb
+ wc = j.createWebClient();
+ wc.withBasicCredentials("foo");
+ makeRequestAndFail(403);
+
+ wc = j.createWebClient();
+ wc.login("foo");
+ checkWeCanChangeMyDescription(200);
+ }
+
+ private void makeRequestAndVerify(String expected) throws IOException, SAXException {
+ WebRequest req = new WebRequest(new URL(j.getURL(), "test-post"));
+ req.setHttpMethod(HttpMethod.POST);
+ req.setEncodingType(null);
+ Page p = wc.getPage(req);
+ assertEquals(expected, p.getWebResponse().getContentAsString());
+ }
+
+ private void makeRequestAndFail(int expectedCode) throws IOException, SAXException {
+ try {
+ makeRequestAndVerify("-");
+ fail();
+ } catch (FailingHttpStatusCodeException e) {
+ assertEquals(expectedCode, e.getStatusCode());
+ }
+ }
+
+ private void checkWeCanChangeMyDescription(int expectedCode) throws IOException, SAXException {
+ HtmlPage page = wc.goTo("me/configure");
+ HtmlForm form = page.getFormByName("config");
+ form.getTextAreaByName("_.description").setText("random description: " + Math.random());
+
+ Page result = HtmlFormUtil.submit(form);
+ assertEquals(expectedCode, result.getWebResponse().getStatusCode());
+ }
+
+ @TestExtension
+ public static class WhoAmI implements UnprotectedRootAction {
+ @Override
+ public String getIconFileName() {
+ return null;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "test-post";
+ }
+
+ public HttpResponse doIndex() {
+ User u = User.current();
+ return HttpResponses.text(u != null ? u.getId() : "anonymous");
+ }
+ }
+}
diff --git a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java
index f5e746d161..8737d26974 100644
--- a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java
+++ b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java
@@ -121,7 +121,6 @@ public class ApiTokenPropertyTest {
wc.getOptions().setThrowExceptionOnFailingStatusCode(true);
WebRequest request = new WebRequest(new URL(j.getURL().toString() + foo.getUrl() + "/" + descriptor.getDescriptorUrl()+ "/changeToken"), HttpMethod.POST);
- wc.addCrumb(request);
HtmlPage res = wc.getPage(request);
// TODO This nicer alternative requires https://github.com/jenkinsci/jenkins/pull/2268 or similar to work
diff --git a/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java b/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java
index 449293ae10..0b471dd2b3 100644
--- a/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java
+++ b/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java
@@ -1,15 +1,13 @@
package jenkins.security;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequest;
+import hudson.ExtensionList;
import hudson.model.UnprotectedRootAction;
import hudson.model.User;
import hudson.util.HttpResponses;
-import hudson.util.Scrambler;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
@@ -20,6 +18,10 @@ import org.xml.sax.SAXException;
import java.io.IOException;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import static org.junit.Assert.*;
/**
* @author Kohsuke Kawaguchi
@@ -30,78 +32,82 @@ public class BasicHeaderProcessorTest {
private WebClient wc;
+ private SpySecurityListener spySecurityListener;
+
+ @Before
+ public void prepareListeners(){
+ //TODO simplify using #3021 into ExtensionList.lookupSingleton(SpySecurityListener.class)
+ this.spySecurityListener = ExtensionList.lookup(SecurityListener.class).get(SpySecurityListenerImpl.class);
+ }
+
/**
* Tests various ways to send the Basic auth.
*/
@Test
public void testVariousWaysToCall() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
- User foo = User.get("foo");
- User bar = User.get("bar");
+ User foo = User.getById("foo", true);
+ User.getById("bar", true);
wc = j.createWebClient();
// call without authentication
- makeRequestWithAuthAndVerify(null, "anonymous");
+ makeRequestAndVerify("anonymous");
+ spySecurityListener.authenticatedCalls.assertNoNewEvents();
+ spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents();
// call with API token
- ApiTokenProperty t = foo.getProperty(ApiTokenProperty.class);
- final String token = t.getApiToken();
- makeRequestWithAuthAndVerify("foo:"+token, "foo");
+ wc = j.createWebClient();
+ wc.withBasicApiToken("foo");
+ makeRequestAndVerify("foo");
+ spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo"));
// call with invalid API token
- makeRequestAndFail("foo:abcd"+token);
+ wc = j.createWebClient();
+ wc.withBasicCredentials("foo", "abcd" + foo.getProperty(ApiTokenProperty.class).getApiToken());
+ makeRequestAndFail();
+ spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo");
// call with password
- makeRequestWithAuthAndVerify("foo:foo", "foo");
+ wc = j.createWebClient();
+ wc.withBasicCredentials("foo");
+ makeRequestAndVerify("foo");
+ spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo"));
// call with incorrect password
- makeRequestAndFail("foo:bar");
-
+ wc = j.createWebClient();
+ wc.withBasicCredentials("foo", "bar");
+ makeRequestAndFail();
+ spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo");
+ wc = j.createWebClient();
wc.login("bar");
+ spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("bar"));
+ spySecurityListener.loggedInCalls.assertLastEventIsAndThenRemoveIt("bar");
// if the session cookie is valid, then basic header won't be needed
- makeRequestWithAuthAndVerify(null, "bar");
+ makeRequestAndVerify("bar");
+ spySecurityListener.authenticatedCalls.assertNoNewEvents();
+ spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents();
// if the session cookie is valid, and basic header is set anyway login should not fail either
- makeRequestWithAuthAndVerify("bar:bar", "bar");
+ wc.withBasicCredentials("bar");
+ makeRequestAndVerify("bar");
+ spySecurityListener.authenticatedCalls.assertNoNewEvents();
+ spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents();
// but if the password is incorrect, it should fail, instead of silently logging in as the user indicated by session
- makeRequestAndFail("foo:bar");
+ wc.withBasicCredentials("foo", "bar");
+ makeRequestAndFail();
+ spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo");
}
- private void makeRequestAndFail(String userAndPass) throws IOException, SAXException {
- makeRequestWithAuthCodeAndFail(encrypt("Basic", userAndPass));
- }
-
- private String encrypt(String prefix, String userAndPass) {
- if (userAndPass==null) {
- return null;
- }
- return prefix+" "+Scrambler.scramble(userAndPass);
+ private void makeRequestAndFail() throws IOException, SAXException {
+ makeRequestWithAuthCodeAndFail(null);
}
- private void makeRequestWithAuthCodeAndFail(String authCode) throws IOException, SAXException {
- try {
- makeRequestWithAuthCodeAndVerify(authCode, "-");
- fail();
- } catch (FailingHttpStatusCodeException e) {
- assertEquals(401, e.getStatusCode());
- }
- }
-
- private void makeRequestWithAuthAndVerify(String userAndPass, String username) throws IOException, SAXException {
- makeRequestWithAuthCodeAndVerify(encrypt("Basic", userAndPass), username);
- }
-
- private void makeRequestWithAuthCodeAndVerify(String authCode, String expected) throws IOException, SAXException {
- WebRequest req = new WebRequest(new URL(j.getURL(),"test"));
- req.setEncodingType(null);
- if (authCode!=null)
- req.setAdditionalHeader("Authorization", authCode);
- Page p = wc.getPage(req);
- assertEquals(expected, p.getWebResponse().getContentAsString().trim());
+ private void makeRequestAndVerify(String expectedLogin) throws IOException, SAXException {
+ makeRequestWithAuthCodeAndVerify(null, expectedLogin);
}
@Test
@@ -111,25 +117,54 @@ public class BasicHeaderProcessorTest {
wc = j.createWebClient();
String[] basicCandidates = {"Basic", "BASIC", "basic", "bASIC"};
-
+
for (String prefix : basicCandidates) {
// call with API token
ApiTokenProperty t = foo.getProperty(ApiTokenProperty.class);
final String token = t.getApiToken();
- String authCode1 = encrypt(prefix,"foo:"+token);
+ String authCode1 = encode(prefix,"foo:"+token);
makeRequestWithAuthCodeAndVerify(authCode1, "foo");
-
+ spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo"));
+
// call with invalid API token
- String authCode2 = encrypt(prefix,"foo:abcd"+token);
+ String authCode2 = encode(prefix,"foo:abcd"+token);
makeRequestWithAuthCodeAndFail(authCode2);
+ spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo");
// call with password
- String authCode3 = encrypt(prefix,"foo:foo");
+ String authCode3 = encode(prefix,"foo:foo");
makeRequestWithAuthCodeAndVerify(authCode3, "foo");
+ spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo"));
// call with incorrect password
- String authCode4 = encrypt(prefix,"foo:bar");
+ String authCode4 = encode(prefix,"foo:bar");
makeRequestWithAuthCodeAndFail(authCode4);
+ spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo");
+ }
+ }
+
+ private String encode(String prefix, String userAndPass) {
+ if (userAndPass==null) {
+ return null;
+ }
+ return prefix + " " + Base64.getEncoder().encodeToString(userAndPass.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private void makeRequestWithAuthCodeAndVerify(String authCode, String expectedLogin) throws IOException, SAXException {
+ WebRequest req = new WebRequest(new URL(j.getURL(),"test"));
+ req.setEncodingType(null);
+ if (authCode!=null)
+ req.setAdditionalHeader("Authorization", authCode);
+ Page p = wc.getPage(req);
+ assertEquals(expectedLogin, p.getWebResponse().getContentAsString());
+ }
+
+ private void makeRequestWithAuthCodeAndFail(String authCode) throws IOException, SAXException {
+ try {
+ makeRequestWithAuthCodeAndVerify(authCode, "-");
+ fail();
+ } catch (FailingHttpStatusCodeException e) {
+ assertEquals(401, e.getStatusCode());
}
}
@@ -152,7 +187,10 @@ public class BasicHeaderProcessorTest {
public HttpResponse doIndex() {
User u = User.current();
- return HttpResponses.plainText(u!=null ? u.getId() : "anonymous");
+ return HttpResponses.text(u!=null ? u.getId() : "anonymous");
}
}
+
+ @TestExtension
+ public static class SpySecurityListenerImpl extends SpySecurityListener {}
}
diff --git a/test/src/test/java/jenkins/security/Security380Test.java b/test/src/test/java/jenkins/security/Security380Test.java
index 5d3475df92..c7b3e90a6f 100644
--- a/test/src/test/java/jenkins/security/Security380Test.java
+++ b/test/src/test/java/jenkins/security/Security380Test.java
@@ -67,6 +67,7 @@ public class Security380Test {
JenkinsRule.WebClient wc = j.createWebClient();
Page page = wc.goTo("listJobs", "text/plain");
+ // return "0\r\n"
Assert.assertEquals("expect 0 items", "0", page.getWebResponse().getContentAsString().trim());
}
diff --git a/test/src/test/java/jenkins/security/SpySecurityListener.java b/test/src/test/java/jenkins/security/SpySecurityListener.java
new file mode 100644
index 0000000000..0d9731cc90
--- /dev/null
+++ b/test/src/test/java/jenkins/security/SpySecurityListener.java
@@ -0,0 +1,115 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2017, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package jenkins.security;
+
+import org.acegisecurity.userdetails.UserDetails;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+//TODO temporary solution, should be moved to Jenkins Test Harness project
+/**
+ * Mean to be included in test classes to provide a way to spy on the SecurityListener events
+ * @see jenkins.security.BasicHeaderProcessorTest.SpySecurityListenerImpl
+ * @see hudson.security.CliAuthenticationTest.SpySecurityListenerImpl
+ */
+public abstract class SpySecurityListener extends SecurityListener {
+ public final EventQueue authenticatedCalls = new EventQueue<>();
+ public final EventQueue failedToAuthenticateCalls = new EventQueue<>();
+ public final EventQueue loggedInCalls = new EventQueue<>();
+ public final EventQueue failedToLogInCalls = new EventQueue<>();
+ public final EventQueue loggedOutCalls = new EventQueue<>();
+
+ public void clearPreviousCalls(){
+ this.authenticatedCalls.clear();
+ this.failedToAuthenticateCalls.clear();
+ this.loggedInCalls.clear();
+ this.failedToLogInCalls.clear();
+ this.loggedOutCalls.clear();
+ }
+
+ @Override
+ protected void authenticated(@Nonnull UserDetails details) {
+ this.authenticatedCalls.add(details);
+ }
+
+ @Override
+ protected void failedToAuthenticate(@Nonnull String username) {
+ this.failedToAuthenticateCalls.add(username);
+ }
+
+ @Override
+ protected void loggedIn(@Nonnull String username) {
+ this.loggedInCalls.add(username);
+ }
+
+ @Override
+ protected void failedToLogIn(@Nonnull String username) {
+ this.failedToLogInCalls.add(username);
+ }
+
+ @Override
+ protected void loggedOut(@Nonnull String username) {
+ this.loggedOutCalls.add(username);
+
+ }
+
+ public static class EventQueue {
+ private final List eventList = new ArrayList<>();
+
+ private EventQueue add(T t){
+ eventList.add(t);
+ return this;
+ }
+
+ public void assertLastEventIsAndThenRemoveIt(T expected){
+ assertLastEventIsAndThenRemoveIt(expected::equals);
+ }
+
+ public void assertLastEventIsAndThenRemoveIt(Predicate predicate){
+ if(eventList.isEmpty()){
+ fail("event list is empty");
+ }
+
+ T t = eventList.remove(eventList.size() - 1);
+ assertTrue(predicate.test(t));
+ eventList.clear();
+ }
+
+ public void assertNoNewEvents(){
+ assertEquals("list of event should be empty", eventList.size(), 0);
+ }
+
+ public void clear(){
+ eventList.clear();
+ }
+ }
+}
+
diff --git a/test/src/test/java/lib/form/ApplyButtonTest.java b/test/src/test/java/lib/form/ApplyButtonTest.java
new file mode 100644
index 0000000000..028e135da0
--- /dev/null
+++ b/test/src/test/java/lib/form/ApplyButtonTest.java
@@ -0,0 +1,42 @@
+package lib.form;
+
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import hudson.markup.RawHtmlMarkupFormatter;
+import hudson.model.FreeStyleBuild;
+import hudson.model.FreeStyleProject;
+import org.apache.commons.io.IOUtils;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+
+public class ApplyButtonTest {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ /**
+ * Editing code mirror should still gets reflected when you click apply.
+ */
+ @Test
+ @Issue("JENKINS-18436")
+ public void editDescription() throws Exception {
+ j.jenkins.setMarkupFormatter(RawHtmlMarkupFormatter.INSTANCE); // need something using CodeMirror
+ FreeStyleProject p = j.createFreeStyleProject();
+ FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
+
+ HtmlPage config = j.createWebClient().getPage(b, "configure");
+ HtmlForm form = config.getFormByName("config");
+ // HtmlUnit doesn't have JSON, so we need to emulate one
+ config.executeJavaScript(IOUtils.toString(ApplyButtonTest.class.getResource("JSON.js")));
+ // it's hard to emulate the keytyping, so we just set the value into codemirror and test if this gets
+ // reflected back into TEXTAREA
+ config.executeJavaScript("document.getElementsByTagName('TEXTAREA')[0].codemirrorObject.setLine(0,'foobar')");
+ j.getButtonByCaption(form, "Apply").click();
+
+ assertEquals("foobar", b.getDescription());
+ }
+
+}
diff --git a/test/src/test/java/lib/form/PasswordTest.java b/test/src/test/java/lib/form/PasswordTest.java
index 0e3ed7c783..8eef7d40ff 100644
--- a/test/src/test/java/lib/form/PasswordTest.java
+++ b/test/src/test/java/lib/form/PasswordTest.java
@@ -27,16 +27,9 @@ import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
-import hudson.Extension;
import hudson.cli.CopyJobCommand;
import hudson.cli.GetJobCommand;
-import hudson.model.Describable;
-import hudson.model.Descriptor;
-import hudson.model.FreeStyleProject;
-import hudson.model.Item;
-import hudson.model.JobProperty;
-import hudson.model.JobPropertyDescriptor;
-import hudson.model.User;
+import hudson.model.*;
import hudson.util.FormValidation;
import hudson.util.Secret;
import java.io.ByteArrayOutputStream;
@@ -51,8 +44,10 @@ import org.acegisecurity.Authentication;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertThat;
-import org.jvnet.hudson.test.HudsonTestCase;
+import static org.junit.Assert.*;
+
+import org.junit.Rule;
+import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;
@@ -62,25 +57,43 @@ import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
-public class PasswordTest extends HudsonTestCase implements Describable {
- public Secret secret;
+public class PasswordTest {
- public void test1() throws Exception {
- secret = Secret.fromString("secret");
- HtmlPage p = createWebClient().goTo("self/test1");
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ @Test
+ public void secretNotPlainText() throws Exception {
+ SecretNotPlainText.secret = Secret.fromString("secret");
+ HtmlPage p = j.createWebClient().goTo("secretNotPlainText");
String value = ((HtmlInput)p.getElementById("password")).getValueAttribute();
assertFalse("password shouldn't be plain text",value.equals("secret"));
assertEquals("secret",Secret.fromString(value).getPlainText());
}
- public DescriptorImpl getDescriptor() {
- return jenkins.getDescriptorByType(DescriptorImpl.class);
+ @TestExtension("secretNotPlainText")
+ public static class SecretNotPlainText implements RootAction {
+
+ public static Secret secret;
+
+ @Override
+ public String getIconFileName() {
+ return null;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "secretNotPlainText";
+ }
}
- @Extension
- public static final class DescriptorImpl extends Descriptor {}
-
@Issue({"SECURITY-266", "SECURITY-304"})
+ @Test
public void testExposedCiphertext() throws Exception {
boolean saveEnabled = Item.EXTENDED_READ.getEnabled();
Item.EXTENDED_READ.setEnabled(true);
@@ -93,24 +106,28 @@ public class PasswordTest extends HudsonTestCase implements Describable {
+
+ String text1, text2;
+
+ @Override
+ public boolean isApplicable(Class extends AbstractProject> jobType) {
+ return true;
+ }
+
+ public FormValidation doCheckText1(@QueryParameter String value) {
+ this.text1 = value;
+ return FormValidation.ok();
+ }
+
+ public FormValidation doCheckText2(@QueryParameter String text1) {
+ this.text2 = "Received " + text1;
+ return FormValidation.ok();
+ }
+ }
+
+ }
+
+ @Issue("JENKINS-27505")
+ @Test
+ public void text() throws Exception {
+ T1: {
+ String TEXT_TO_TEST = "some\nvalue\n";
+ FreeStyleProject p = j.createFreeStyleProject();
+ TextareaTestBuilder target = new TextareaTestBuilder(TEXT_TO_TEST);
+ p.getBuildersList().add(target);
+ j.configRoundtrip(p);
+ j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class));
+ }
+
+ // test for a textarea beginning with a empty line.
+ T2: {
+ String TEXT_TO_TEST = "\nbegin\n\nwith\nempty\nline\n\n";
+ FreeStyleProject p = j.createFreeStyleProject();
+ TextareaTestBuilder target = new TextareaTestBuilder(TEXT_TO_TEST);
+ p.getBuildersList().add(target);
+ j.configRoundtrip(p);
+ j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class));
+ }
+
+ // test for a textarea beginning with two empty lines.
+ T3: {
+ String TEXT_TO_TEST = "\n\nbegin\n\nwith\ntwo\nempty\nline\n\n";
+ FreeStyleProject p = j.createFreeStyleProject();
+ TextareaTestBuilder target = new TextareaTestBuilder(TEXT_TO_TEST);
+ p.getBuildersList().add(target);
+ j.configRoundtrip(p);
+ j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class));
+ }
+ }
+
+ public static class TextareaTestBuilder extends Builder {
+
+ private String text;
+
+ @DataBoundConstructor
+ public TextareaTestBuilder(String text) {
+ this.text = text;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ @TestExtension
+ public static class DescriptorImpl extends BuildStepDescriptor {
+ @Override
+ public boolean isApplicable(Class extends AbstractProject> jobType) {
+ return true;
+ }
+
+ }
+
+ }
+
+}
diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml
new file mode 100644
index 0000000000..6fcea351c2
--- /dev/null
+++ b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml
@@ -0,0 +1,34 @@
+
+
+
+ 1.550
+ 2
+ NORMAL
+ true
+
+
+ false
+
+ ${JENKINS_HOME}/workspace/${ITEM_FULLNAME}
+ ${ITEM_ROOTDIR}/builds
+
+
+
+
+ 5
+ 0
+
+
+
+ All
+ false
+ false
+
+
+
+ All
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion
new file mode 100644
index 0000000000..415b19fc36
--- /dev/null
+++ b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion
@@ -0,0 +1 @@
+2.0
\ No newline at end of file
diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithNewerDependency.zip b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithNewerDependency.zip
new file mode 100644
index 0000000000..557841273a
Binary files /dev/null and b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithNewerDependency.zip differ
diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithOlderDependency.zip b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithOlderDependency.zip
new file mode 100644
index 0000000000..b85561cf0d
Binary files /dev/null and b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithOlderDependency.zip differ
diff --git a/test/src/test/resources/lib/form/PasswordTest/test1.jelly b/test/src/test/resources/lib/form/PasswordTest/SecretNotPlainText/index.jelly
similarity index 91%
rename from test/src/test/resources/lib/form/PasswordTest/test1.jelly
rename to test/src/test/resources/lib/form/PasswordTest/SecretNotPlainText/index.jelly
index 94fc58a272..c0ecc9595b 100644
--- a/test/src/test/resources/lib/form/PasswordTest/test1.jelly
+++ b/test/src/test/resources/lib/form/PasswordTest/SecretNotPlainText/index.jelly
@@ -26,7 +26,7 @@ THE SOFTWARE.
Config page
-->
-
+
diff --git a/war/pom.xml b/war/pom.xml
index 3d7ea1e54e..9147f0ecc3 100644
--- a/war/pom.xml
+++ b/war/pom.xml
@@ -28,7 +28,7 @@ THE SOFTWARE.
org.jenkins-ci.main
pom
- 2.94-SNAPSHOT
+ 2.97-SNAPSHOT
jenkins-war
@@ -130,7 +130,7 @@ THE SOFTWARE.
org.jenkins-ci.modules
sshd
- 2.0
+ 2.3
org.jenkins-ci.ui
diff --git a/war/src/main/webapp/help/parameter/trim_zh_CN.html b/war/src/main/webapp/help/parameter/trim_zh_CN.html
new file mode 100644
index 0000000000..a3c56e13b7
--- /dev/null
+++ b/war/src/main/webapp/help/parameter/trim_zh_CN.html
@@ -0,0 +1,3 @@
+
+ 清除字符串前后的清白字符。
+