提交 f09d00dd 编写于 作者: O Oliver Gondža

Merge branch 'master' of github.com:jenkinsci/jenkins

......@@ -549,7 +549,7 @@ public class CLI implements AutoCloseable {
for (Handler h : Logger.getLogger("").getHandlers()) {
h.setLevel(level);
}
for (Logger logger : new Logger[] {LOGGER, PlainCLIProtocol.LOGGER, Logger.getLogger("org.apache.sshd")}) { // perhaps also Channel
for (Logger logger : new Logger[] {LOGGER, FullDuplexHttpStream.LOGGER, PlainCLIProtocol.LOGGER, Logger.getLogger("org.apache.sshd")}) { // perhaps also Channel
logger.setLevel(level);
}
args = args.subList(2, args.size());
......@@ -704,6 +704,22 @@ public class CLI implements AutoCloseable {
}
}
}.start();
new Thread("ping") { // JENKINS-46659
@Override
public void run() {
try {
Thread.sleep(10_000);
while (!connection.complete) {
LOGGER.fine("sending ping");
connection.sendEncoding(Charset.defaultCharset().name()); // no-op at this point
Thread.sleep(10_000);
}
} catch (IOException | InterruptedException x) {
LOGGER.log(Level.WARNING, null, x);
}
}
}.start();
synchronized (connection) {
while (!connection.complete) {
connection.wait();
......
package hudson.cli;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
......@@ -16,7 +14,7 @@ import org.apache.commons.codec.binary.Base64;
/**
* Creates a capacity-unlimited bi-directional {@link InputStream}/{@link OutputStream} pair over
* HTTP, which is a request/response protocol.
*
* {@code FullDuplexHttpService} is the counterpart on the server side.
* @author Kohsuke Kawaguchi
*/
public class FullDuplexHttpStream {
......@@ -29,10 +27,18 @@ public class FullDuplexHttpStream {
private final OutputStream output;
private final InputStream input;
/**
* A way to get data from the server.
* There will be an initial zero byte used as a handshake which you should expect and ignore.
*/
public InputStream getInputStream() {
return input;
}
/**
* A way to upload data to the server.
* You will need to write to this and {@link OutputStream#flush} it to finish establishing a connection.
*/
public OutputStream getOutputStream() {
return output;
}
......@@ -79,11 +85,10 @@ public class FullDuplexHttpStream {
URL target = new URL(this.base, relativeTarget);
CrumbData crumbData = new CrumbData();
UUID uuid = UUID.randomUUID(); // so that the server can correlate those two connections
// server->client
LOGGER.fine("establishing download side");
HttpURLConnection con = (HttpURLConnection) target.openConnection();
con.setDoOutput(true); // request POST to avoid caching
con.setRequestMethod("POST");
......@@ -92,17 +97,16 @@ public class FullDuplexHttpStream {
if (authorization != null) {
con.addRequestProperty("Authorization", authorization);
}
if(crumbData.isValid) {
con.addRequestProperty(crumbData.crumbName, crumbData.crumb);
}
con.getOutputStream().close();
input = con.getInputStream();
// make sure we hit the right URL
// make sure we hit the right URL; no need for CLI.verifyJenkinsConnection here
if (con.getHeaderField("Hudson-Duplex") == null) {
throw new CLI.NotTalkingToJenkinsException("There's no Jenkins running at " + target + ", or is not serving the HTTP Duplex transport");
}
LOGGER.fine("established download side"); // calling getResponseCode or getHeaderFields breaks everything
// client->server uses chunked encoded POST for unlimited capacity.
// client->server uses chunked encoded POST for unlimited capacity.
LOGGER.fine("establishing upload side");
con = (HttpURLConnection) target.openConnection();
con.setDoOutput(true); // request POST
con.setRequestMethod("POST");
......@@ -113,11 +117,8 @@ public class FullDuplexHttpStream {
if (authorization != null) {
con.addRequestProperty ("Authorization", authorization);
}
if(crumbData.isValid) {
con.addRequestProperty(crumbData.crumbName, crumbData.crumb);
}
output = con.getOutputStream();
LOGGER.fine("established upload side");
}
// As this transport mode is using POST, it is necessary to resolve possible redirections using GET first.
......@@ -141,59 +142,4 @@ public class FullDuplexHttpStream {
static final int BLOCK_SIZE = 1024;
static final Logger LOGGER = Logger.getLogger(FullDuplexHttpStream.class.getName());
private final class CrumbData {
String crumbName;
String crumb;
boolean isValid;
private CrumbData() {
this.crumbName = "";
this.crumb = "";
this.isValid = false;
getData();
}
private void getData() {
try {
String base = createCrumbUrlBase();
String[] pair = readData(base + "?xpath=concat(//crumbRequestField,\":\",//crumb)").split(":", 2);
crumbName = pair[0];
crumb = pair[1];
isValid = true;
LOGGER.fine("Crumb data: "+crumbName+"="+crumb);
} catch (IOException e) {
// presumably this Hudson doesn't use crumb
LOGGER.log(Level.FINE,"Failed to get crumb data",e);
}
}
private String createCrumbUrlBase() {
return base + "crumbIssuer/api/xml/";
}
private String readData(String dest) throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(dest).openConnection();
if (authorization != null) {
con.addRequestProperty("Authorization", authorization);
}
CLI.verifyJenkinsConnection(con);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
String line = reader.readLine();
String nextLine = reader.readLine();
if (nextLine != null) {
System.err.println("Warning: received junk from " + dest);
System.err.println(line);
System.err.println(nextLine);
while ((nextLine = reader.readLine()) != null) {
System.err.println(nextLine);
}
}
return line;
}
finally {
con.disconnect();
}
}
}
}
......@@ -34,7 +34,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadPendingException;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
......@@ -112,13 +111,6 @@ class PlainCLIProtocol {
} catch (EOFException x) {
handleClose();
break; // TODO verify that we hit EOF immediately, not partway into framelen
} catch (IOException x) {
if (x.getCause() instanceof TimeoutException) { // TODO on Tomcat this seems to be SocketTimeoutException
LOGGER.log(Level.FINE, "ignoring idle timeout, perhaps from Jetty", x);
continue;
} else {
throw x;
}
}
if (framelen < 0) {
throw new IOException("corrupt stream: negative frame length");
......
......@@ -567,6 +567,25 @@ public class Util {
return true;
}
/**
* A check if a file path is a descendant of a parent path
* @param forParent the parent the child should be a descendant of
* @param potentialChild the path to check
* @return true if so
* @throws IOException for invalid paths
* @since FIXME
* @see InvalidPathException
*/
public static boolean isDescendant(File forParent, File potentialChild) throws IOException {
try {
Path child = potentialChild.getAbsoluteFile().toPath().normalize();
Path parent = forParent.getAbsoluteFile().toPath().normalize();
return child.startsWith(parent);
} catch (InvalidPathException e) {
throw new IOException(e);
}
}
/**
* Creates a new temporary directory.
*/
......
......@@ -31,7 +31,6 @@ import java.nio.file.StandardOpenOption;
import jenkins.util.SystemProperties;
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import com.thoughtworks.xstream.core.JVM;
import com.trilead.ssh2.util.IOUtils;
import hudson.model.Hudson;
import hudson.security.ACL;
import hudson.util.BootFailure;
......@@ -62,7 +61,6 @@ import javax.servlet.ServletResponse;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
......
......@@ -53,6 +53,8 @@ import java.net.HttpURLConnection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Used to expose remote access API for ".../api/"
......@@ -228,7 +230,8 @@ public class Api extends AbstractModelObject {
return false;
}
private void setHeaders(StaplerResponse rsp) {
@Restricted(NoExternalUse.class)
protected void setHeaders(StaplerResponse rsp) {
rsp.setHeader("X-Jenkins", Jenkins.VERSION);
rsp.setHeader("X-Jenkins-Session", Jenkins.SESSION_HASH);
}
......
......@@ -29,6 +29,7 @@ import hudson.EnvVars;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.FeedAdapter;
import hudson.FilePath;
import hudson.PermalinkList;
import hudson.Util;
import hudson.cli.declarative.CLIResolver;
......@@ -68,6 +69,7 @@ import java.awt.Paint;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
......@@ -680,6 +682,40 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
Util.deleteRecursive(getBuildDir());
}
@Restricted(NoExternalUse.class)
@Extension
public static class SubItemBuildsLocationImpl extends ItemListener {
@Override
public void onLocationChanged(Item item, String oldFullName, String newFullName) {
final Jenkins jenkins = Jenkins.getInstance();
if (!jenkins.isDefaultBuildDir() && item instanceof Job) {
File newBuildDir = ((Job)item).getBuildDir();
try {
if (!Util.isDescendant(item.getRootDir(), newBuildDir)) {
//OK builds are stored somewhere outside of the item's root, so none of the other move operations has probably moved it.
//So let's try even though we lack some information
String oldBuildsDir = Jenkins.expandVariablesForDirectory(jenkins.getRawBuildsDir(), oldFullName, "<NOPE>");
if (oldBuildsDir.contains("<NOPE>")) {
LOGGER.severe(String.format("Builds directory for job %1$s appears to be outside of item root," +
" but somehow still containing the item root path, which is unknown. Cannot move builds from %2$s to %1$s.", newFullName, oldFullName));
} else {
File oldDir = new File(oldBuildsDir);
if (oldDir.isDirectory()) {
try {
FileUtils.moveDirectory(oldDir, newBuildDir);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, String.format("Failed to move %s to %s", oldBuildsDir, newBuildDir.getAbsolutePath()), e);
}
}
}
}
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to inspect " + item.getRootDir() + ". Builds might not be moved.", e);
}
}
}
}
/**
* Returns true if we should display "build now" icon
*/
......
......@@ -45,6 +45,7 @@ import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.annotation.CheckForNull;
import javax.annotation.concurrent.GuardedBy;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
......@@ -378,13 +379,16 @@ public class ListView extends View implements DirectlyModifiableView {
throw new Failure("Query parameter 'name' is required");
TopLevelItem item = resolveName(name);
if (item==null)
throw new Failure("Query parameter 'name' does not correspond to a known and readable item");
if (remove(item))
owner.save();
return HttpResponses.ok();
}
private TopLevelItem resolveName(String name) {
private @CheckForNull TopLevelItem resolveName(String name) {
TopLevelItem item = getOwner().getItemGroup().getItem(name);
if (item == null) {
name = Items.getCanonicalName(getOwner().getItemGroup(), name);
......
......@@ -193,6 +193,7 @@ public abstract class CrumbIssuer implements Describable<CrumbIssuer>, Extension
}
@Override public void doXml(StaplerRequest req, StaplerResponse rsp, @QueryParameter String xpath, @QueryParameter String wrapper, @QueryParameter String tree, @QueryParameter int depth) throws IOException, ServletException {
setHeaders(rsp);
String text;
CrumbIssuer ci = (CrumbIssuer) bean;
if ("/*/crumbRequestField/text()".equals(xpath)) { // old FullDuplexHttpStream
......
......@@ -409,7 +409,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* This value will be variable-expanded as per {@link #expandVariablesForDirectory}.
* @see #getBuildDirFor(Job)
*/
private String buildsDir = "${ITEM_ROOTDIR}/builds";
private String buildsDir = DEFAULT_BUILDS_DIR;
/**
* Message displayed in the top page.
......@@ -2439,12 +2439,22 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
return expandVariablesForDirectory(buildsDir, job);
}
/**
* If the configured buildsDir has it's default value or has been changed.
*
* @return true if default value.
*/
@Restricted(NoExternalUse.class)
public boolean isDefaultBuildDir() {
return DEFAULT_BUILDS_DIR.equals(buildsDir);
}
private File expandVariablesForDirectory(String base, Item item) {
return new File(expandVariablesForDirectory(base, item.getFullName(), item.getRootDir().getPath()));
}
@Restricted(NoExternalUse.class)
static String expandVariablesForDirectory(String base, String itemFullName, String itemRootDir) {
public static String expandVariablesForDirectory(String base, String itemFullName, String itemRootDir) {
return Util.replaceMacro(base, ImmutableMap.of(
"JENKINS_HOME", Jenkins.getInstance().getRootDir().getPath(),
"ITEM_ROOTDIR", itemRootDir,
......@@ -3181,6 +3191,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
public synchronized void save() throws IOException {
if(BulkChange.contains(this)) return;
version = VERSION;
getConfigFile().write(this);
SaveableListener.fireOnChange(this, getConfigFile());
}
......@@ -3658,9 +3670,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
boolean result = true;
for (Descriptor<?> d : Functions.getSortedDescriptorsForGlobalConfigUnclassified())
result &= configureDescriptor(req,json,d);
version = VERSION;
save();
updateComputerList();
if(result)
......@@ -5068,6 +5078,12 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
private static final String WORKSPACE_DIRNAME = Configuration.getStringConfigParameter("workspaceDirName", "workspace");
/**
* Default value of job's builds dir.
* @see #getRawBuildsDir()
*/
private static final String DEFAULT_BUILDS_DIR = "${ITEM_ROOTDIR}/builds";
/**
* Automatically try to launch an agent when Jenkins is initialized or a new agent computer is created.
*/
......
......@@ -24,6 +24,8 @@
package jenkins.util;
import hudson.cli.FullDuplexHttpStream;
import hudson.model.RootAction;
import hudson.security.csrf.CrumbExclusion;
import hudson.util.ChunkedInputStream;
import hudson.util.ChunkedOutputStream;
import java.io.IOException;
......@@ -44,6 +46,8 @@ import org.kohsuke.stapler.StaplerResponse;
/**
* Server-side counterpart to {@link FullDuplexHttpStream}.
* <p>
* To use, bind this to an endpoint with {@link RootAction} (you will also need a {@link CrumbExclusion}).
* @since 2.54
*/
public abstract class FullDuplexHttpService {
......
......@@ -672,4 +672,42 @@ public class UtilTest {
}
}
@Test
public void testIsDescendant() throws IOException {
File root;
File other;
if (Functions.isWindows()) {
root = new File("C:\\Temp");
other = new File("C:\\Windows");
} else {
root = new File("/tmp");
other = new File("/usr");
}
assertTrue(Util.isDescendant(root, new File(root,"child")));
assertTrue(Util.isDescendant(root, new File(new File(root,"child"), "grandchild")));
assertFalse(Util.isDescendant(root, other));
assertFalse(Util.isDescendant(root, new File(other, "child")));
assertFalse(Util.isDescendant(new File(root,"child"), root));
assertFalse(Util.isDescendant(new File(new File(root,"child"), "grandchild"), root));
//.. whithin root
File convoluted = new File(root, "child");
convoluted = new File(convoluted, "..");
convoluted = new File(convoluted, "child");
assertTrue(Util.isDescendant(root, convoluted));
//.. going outside of root
convoluted = new File(root, "..");
convoluted = new File(convoluted, other.getName());
convoluted = new File(convoluted, "child");
assertFalse(Util.isDescendant(root, convoluted));
//. on root
assertTrue(Util.isDescendant(new File(root, "."), new File(root, "child")));
//. on both
assertTrue(Util.isDescendant(new File(root, "."), new File(new File(root, "child"), ".")));
}
}
......@@ -280,6 +280,14 @@ THE SOFTWARE.
</rules>
</configuration>
</plugin>
<plugin> <!-- https://stackoverflow.com/a/19503679/12916 ysoserial deliberately uses internal APIs; suppress warnings about them -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-XDignore.symbol.file</compilerArgument>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
......
package hudson.cli;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.google.common.collect.Lists;
import hudson.Functions;
import hudson.Launcher;
......@@ -26,7 +22,6 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
......@@ -40,7 +35,6 @@ import org.apache.commons.io.output.TeeOutputStream;
import org.codehaus.groovy.runtime.Security218;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
......@@ -122,16 +116,6 @@ public class CLIActionTest {
JenkinsRule.WebClient wc = j.createWebClient();
// The behavior changed due to SECURITY-192. index page is no longer accessible to anonymous
wc.assertFails("cli", HttpURLConnection.HTTP_FORBIDDEN);
// so we check the access by emulating the CLI connection post request
WebRequest settings = new WebRequest(new URL(j.getURL(), "cli"));
settings.setHttpMethod(HttpMethod.POST);
settings.setAdditionalHeader("Session", UUID.randomUUID().toString());
settings.setAdditionalHeader("Side", "download"); // We try to download something to init the duplex channel
Page page = wc.getPage(settings);
WebResponse webResponse = page.getWebResponse();
assertEquals("We expect that the proper POST request from CLI gets processed successfully",
200, webResponse.getStatusCode());
}
@Test
......@@ -256,7 +240,6 @@ public class CLIActionTest {
// -ssh mode does not pass client locale or encoding
}
@Ignore("TODO JENKINS-46659 seems to be broken")
@Issue("JENKINS-41745")
@Test
public void interleavedStdio() throws Exception {
......
......@@ -31,17 +31,25 @@ import com.gargoylesoftware.htmlunit.html.HtmlFormUtil;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.TextPage;
import hudson.FilePath;
import hudson.Functions;
import hudson.model.queue.QueueTaskFuture;
import hudson.tasks.ArtifactArchiver;
import hudson.tasks.BatchFile;
import hudson.tasks.Shell;
import hudson.util.TextFile;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.concurrent.TimeUnit;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import jenkins.model.ProjectNamingStrategy;
import org.hamcrest.Matchers;
......@@ -51,6 +59,7 @@ import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.FailureBuilder;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.JenkinsRule.WebClient;
import org.jvnet.hudson.test.MockFolder;
import org.jvnet.hudson.test.RunLoadCounter;
import org.jvnet.hudson.test.SleepBuilder;
import org.jvnet.hudson.test.recipes.LocalData;
......@@ -299,6 +308,107 @@ public class JobTest {
p.renameTo("different-name");
}
@Issue("JENKINS-44657")
@Test public void testRenameWithCustomBuildsDirWithBuildsIntact() throws Exception {
j.jenkins.setRawBuildsDir("${JENKINS_HOME}/builds/${ITEM_FULL_NAME}/builds");
final FreeStyleProject p = j.createFreeStyleProject();
final File oldBuildsDir = p.getBuildDir();
j.buildAndAssertSuccess(p);
String oldDirContent = dirContent(oldBuildsDir);
p.renameTo("different-name");
final File newBuildDir = p.getBuildDir();
assertNotNull(newBuildDir);
assertNotEquals(oldBuildsDir.getAbsolutePath(), newBuildDir.getAbsolutePath());
String newDirContent = dirContent(newBuildDir);
assertEquals(oldDirContent, newDirContent);
}
@Issue("JENKINS-44657")
@Test public void testRenameWithCustomBuildsDirWithBuildsIntactInFolder() throws Exception {
j.jenkins.setRawBuildsDir("${JENKINS_HOME}/builds/${ITEM_FULL_NAME}/builds");
final MockFolder f = j.createFolder("F");
final FreeStyleProject p1 = f.createProject(FreeStyleProject.class, "P1");
j.buildAndAssertSuccess(p1);
File oldP1BuildsDir = p1.getBuildDir();
final String oldP1DirContent = dirContent(oldP1BuildsDir);
f.renameTo("different-name");
File newP1BuildDir = p1.getBuildDir();
assertNotNull(newP1BuildDir);
assertNotEquals(oldP1BuildsDir.getAbsolutePath(), newP1BuildDir.getAbsolutePath());
String newP1DirContent = dirContent(newP1BuildDir);
assertEquals(oldP1DirContent, newP1DirContent);
final FreeStyleProject p2 = f.createProject(FreeStyleProject.class, "P2");
if (Functions.isWindows()) {
p2.getBuildersList().add(new BatchFile("echo hello > hello.txt"));
} else {
p2.getBuildersList().add(new Shell("echo hello > hello.txt"));
}
p2.getPublishersList().add(new ArtifactArchiver("*.txt"));
j.buildAndAssertSuccess(p2);
File oldP2BuildsDir = p2.getBuildDir();
final String oldP2DirContent = dirContent(oldP2BuildsDir);
FreeStyleBuild b2 = p2.getBuilds().getLastBuild();
ByteArrayOutputStream out = new ByteArrayOutputStream();
b2.getLogText().writeRawLogTo(0, out);
final String oldB2Log = new String(out.toByteArray());
assertTrue(b2.getArtifactManager().root().child("hello.txt").exists());
f.renameTo("something-else");
//P1 check again
newP1BuildDir = p1.getBuildDir();
assertNotNull(newP1BuildDir);
assertNotEquals(oldP1BuildsDir.getAbsolutePath(), newP1BuildDir.getAbsolutePath());
newP1DirContent = dirContent(newP1BuildDir);
assertEquals(oldP1DirContent, newP1DirContent);
//P2 check
b2 = p2.getBuilds().getLastBuild();
assertNotNull(b2);
out = new ByteArrayOutputStream();
b2.getLogText().writeRawLogTo(0, out);
final String newB2Log = new String(out.toByteArray());
assertEquals(oldB2Log, newB2Log);
assertTrue(b2.getArtifactManager().root().child("hello.txt").exists());
File newP2BuildDir = p2.getBuildDir();
assertNotNull(newP2BuildDir);
assertNotEquals(oldP2BuildsDir.getAbsolutePath(), newP2BuildDir.getAbsolutePath());
String newP2DirContent = dirContent(newP2BuildDir);
assertEquals(oldP2DirContent, newP2DirContent);
}
private String dirContent(File dir) throws IOException, InterruptedException {
if (dir == null || !dir.isDirectory()) {
return null;
}
StringBuilder str = new StringBuilder("");
final FilePath[] list = new FilePath(dir).list("**/*");
Arrays.sort(list, Comparator.comparing(FilePath::getRemote));
for (FilePath path : list) {
str.append(relativePath(dir, path));
str.append(' ').append(path.length()).append('\n');
}
return str.toString();
}
private String relativePath(File base, FilePath path) throws IOException, InterruptedException {
if (path.absolutize().getRemote().equals(base.getAbsolutePath())) {
return "";
} else {
final String s = relativePath(base, path.getParent());
if (s.isEmpty()) {
return path.getName();
} else {
return s + "/" + path.getName();
}
}
}
@Issue("JENKINS-30502")
@Test
public void testRenameTrimsLeadingSpace() throws Exception {
......
......@@ -259,6 +259,32 @@ public class ListViewTest {
assertEquals("job1", items.get(0).getName());
}
@Issue("JENKINS-23411")
@Test public void doRemoveJobFromViewNullItem() throws Exception {
MockFolder folder = j.createFolder("folder");
ListView view = new ListView("view", folder);
folder.addView(view);
FreeStyleProject job = folder.createProject(FreeStyleProject.class, "job1");
view.add(job);
List<TopLevelItem> items = view.getItems();
assertEquals(1, items.size());
assertEquals("job1", items.get(0).getName());
// remove a contained job
view.doRemoveJobFromView("job1");
List<TopLevelItem> itemsNow = view.getItems();
assertEquals(0, itemsNow.size());
// remove a not contained job
try {
view.doRemoveJobFromView("job2");
fail("Remove job2");
} catch(Failure e) {
assertEquals(e.getMessage(), "Query parameter 'name' does not correspond to a known and readable item");
}
}
private static class AllButViewsAuthorizationStrategy extends AuthorizationStrategy {
@Override public ACL getRootACL() {
return UNSECURED.getRootACL();
......
/*
* The MIT License
*
* Copyright (c) 2015, 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 com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.model.User;
import hudson.security.pages.SignupPage;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import java.util.Collections;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
import org.jvnet.hudson.test.Issue;
/**
* Tests the signup page of {@link hudson.security.HudsonPrivateSecurityRealm}
* TODO: Add to {@link hudson.security.HudsonPrivateSecurityRealmTest} going forward
*/
public class HudsonPrivateSecurityRealm2Test {
@Rule
public JenkinsRule rule = new JenkinsRule();
@Test
public void signup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
rule.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = rule.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("alice");
signup.enterPassword("alice");
signup.enterFullName("Alice User");
signup.enterEmail("alice@nowhere.com");
HtmlPage success = signup.submit(rule);
assertThat(success.getElementById("main-panel").getTextContent(), containsString("Success"));
assertThat(success.getAnchorByHref("/jenkins/user/alice").getTextContent(), containsString("Alice User"));
assertEquals("Alice User", securityRealm.getUser("alice").getDisplayName());
}
@Issue("SECURITY-166")
@Test
public void anonymousCantSignup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
rule.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = rule.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("anonymous");
signup.enterFullName("Bob");
signup.enterPassword("nothing");
signup.enterEmail("noone@nowhere.com");
signup = new SignupPage(signup.submit(rule));
signup.assertErrorContains("prohibited as a username");
assertNull(User.get("anonymous", false, Collections.emptyMap()));
}
@Issue("SECURITY-166")
@Test
public void systemCantSignup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
rule.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = rule.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("system");
signup.enterFullName("Bob");
signup.enterPassword("nothing");
signup.enterEmail("noone@nowhere.com");
signup = new SignupPage(signup.submit(rule));
signup.assertErrorContains("prohibited as a username");
assertNull(User.get("system",false, Collections.emptyMap()));
}
/**
* We don't allow prohibited fullnames since this may encumber auditing.
*/
@Issue("SECURITY-166")
@Test
public void fullNameOfUnknownCantSignup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
rule.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = rule.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("unknown2");
signup.enterPassword("unknown2");
signup.enterFullName("unknown");
signup.enterEmail("noone@nowhere.com");
signup = new SignupPage(signup.submit(rule));
signup.assertErrorContains("prohibited as a full name");
assertNull(User.get("unknown2",false, Collections.emptyMap()));
}
}
/*
* The MIT License
*
* Copyright (c) 2015, CloudBees, Inc. and others
*
* 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 com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import hudson.model.User;
import hudson.remoting.Base64;
import static hudson.security.HudsonPrivateSecurityRealm.CLASSIC;
import static hudson.security.HudsonPrivateSecurityRealm.PASSWORD_ENCODER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertThat;
import hudson.security.pages.SignupPage;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import jenkins.security.ApiTokenProperty;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.xml.HasXPath.hasXPath;
import java.io.UnsupportedEncodingException;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
......@@ -18,17 +46,7 @@ import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.JenkinsRule.WebClient;
import org.jvnet.hudson.test.WithoutJenkins;
import org.jvnet.hudson.test.recipes.LocalData;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import hudson.model.User;
import hudson.remoting.Base64;
import jenkins.security.ApiTokenProperty;
/**
* @author Kohsuke Kawaguchi
*/
public class HudsonPrivateSecurityRealmTest {
@Rule
......@@ -171,4 +189,74 @@ public class HudsonPrivateSecurityRealmTest {
return authHeader;
}
@Test
public void signup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
j.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = j.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("alice");
signup.enterPassword("alice");
signup.enterFullName("Alice User");
signup.enterEmail("alice@nowhere.com");
HtmlPage success = signup.submit(j);
assertThat(success.getElementById("main-panel").getTextContent(), containsString("Success"));
assertThat(success.getAnchorByHref("/jenkins/user/alice").getTextContent(), containsString("Alice User"));
assertEquals("Alice User", securityRealm.getUser("alice").getDisplayName());
}
@Issue("SECURITY-166")
@Test
public void anonymousCantSignup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
j.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = j.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("anonymous");
signup.enterFullName("Bob");
signup.enterPassword("nothing");
signup.enterEmail("noone@nowhere.com");
signup = new SignupPage(signup.submit(j));
signup.assertErrorContains("prohibited as a username");
assertNull(User.get("anonymous", false, Collections.emptyMap()));
}
@Issue("SECURITY-166")
@Test
public void systemCantSignup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
j.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = j.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("system");
signup.enterFullName("Bob");
signup.enterPassword("nothing");
signup.enterEmail("noone@nowhere.com");
signup = new SignupPage(signup.submit(j));
signup.assertErrorContains("prohibited as a username");
assertNull(User.get("system",false, Collections.emptyMap()));
}
/**
* We don't allow prohibited fullnames since this may encumber auditing.
*/
@Issue("SECURITY-166")
@Test
public void fullNameOfUnknownCantSignup() throws Exception {
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null);
j.jenkins.setSecurityRealm(securityRealm);
JenkinsRule.WebClient wc = j.createWebClient();
SignupPage signup = new SignupPage(wc.goTo("signup"));
signup.enterUsername("unknown2");
signup.enterPassword("unknown2");
signup.enterFullName("unknown");
signup.enterEmail("noone@nowhere.com");
signup = new SignupPage(signup.submit(j));
signup.assertErrorContains("prohibited as a full name");
assertNull(User.get("unknown2",false, Collections.emptyMap()));
}
}
......@@ -23,7 +23,6 @@
*/
package jenkins.install;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
......@@ -81,6 +80,9 @@ public class InstallUtilTest {
*/
@Test
public void test_typeTransitions() {
InstallUtil.getLastExecVersionFile().delete();
InstallUtil.getConfigFile().delete();
// A new test instance sets up security first
Assert.assertEquals(InstallState.INITIAL_SECURITY_SETUP, InstallUtil.getNextInstallState(InstallState.UNKNOWN));
......@@ -127,12 +129,10 @@ public class InstallUtilTest {
}
private void setStoredVersion(String version) throws Exception {
Field versionField = Jenkins.class.getDeclaredField("version");
versionField.setAccessible(true);
versionField.set(jenkinsRule.jenkins, version);
Assert.assertEquals(version, Jenkins.getStoredVersion().toString());
Jenkins.VERSION = version;
// Force a save of the config.xml
jenkinsRule.jenkins.save();
Assert.assertEquals(version, Jenkins.getStoredVersion().toString());
}
/**
......
......@@ -62,6 +62,7 @@ import hudson.slaves.ComputerListener;
import hudson.slaves.DumbSlave;
import hudson.slaves.OfflineCause;
import hudson.util.FormValidation;
import hudson.util.VersionNumber;
import org.junit.Rule;
import org.junit.Test;
......@@ -601,4 +602,19 @@ public class JenkinsTest {
assertThat(protocolToDisable2 + " must be disabled after the roundtrip",
j.jenkins.getAgentProtocols(), not(hasItem(protocolToDisable2)));
}
@Issue("JENKINS-42577")
@Test
public void versionIsSavedInSave() throws Exception {
Jenkins.VERSION = "1.0";
j.jenkins.save();
VersionNumber storedVersion = Jenkins.getStoredVersion();
assertNotNull(storedVersion);
assertEquals(storedVersion.toString(), "1.0");
Jenkins.VERSION = null;
j.jenkins.save();
VersionNumber nullVersion = Jenkins.getStoredVersion();
assertNull(nullVersion);
}
}
/*
* 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.util;
import hudson.cli.FullDuplexHttpStream;
import hudson.model.RootAction;
import hudson.security.csrf.CrumbExclusion;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.LoggerRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
public class FullDuplexHttpServiceTest {
@Rule
public JenkinsRule r = new JenkinsRule();
@Rule
public LoggerRule logging = new LoggerRule().record(FullDuplexHttpService.class, Level.FINE).record(FullDuplexHttpStream.class, Level.FINE);
@Test
public void smokes() throws Exception {
logging.record("org.eclipse.jetty", Level.ALL);
FullDuplexHttpStream con = new FullDuplexHttpStream(r.getURL(), "test/", null);
InputStream is = con.getInputStream();
OutputStream os = con.getOutputStream();
os.write(33);
os.flush();
Logger.getLogger(FullDuplexHttpServiceTest.class.getName()).info("uploaded initial content");
assertEquals(0, is.read()); // see FullDuplexHttpStream.getInputStream
assertEquals(66, is.read());
}
@TestExtension("smokes")
public static class Endpoint implements RootAction {
private transient final Map<UUID, FullDuplexHttpService> duplexServices = new HashMap<>();
@Override
public String getUrlName() {
return "test";
}
@Override
public String getIconFileName() {
return null;
}
@Override
public String getDisplayName() {
return null;
}
public HttpResponse doIndex() {
return new FullDuplexHttpService.Response(duplexServices) {
@Override
protected FullDuplexHttpService createService(StaplerRequest req, UUID uuid) throws IOException, InterruptedException {
return new FullDuplexHttpService(uuid) {
@Override
protected void run(InputStream upload, OutputStream download) throws IOException, InterruptedException {
int x = upload.read();
download.write(x * 2);
}
};
}
};
}
}
@TestExtension("smokes")
public static class EndpointCrumbExclusion extends CrumbExclusion {
@Override
public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if ("/test/".equals(request.getPathInfo())) {
chain.doFilter(request, response);
return true;
}
return false;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册