提交 926e5f10 编写于 作者: K Kohsuke Kawaguchi

merged back the RC branch

......@@ -68,9 +68,15 @@ Upcoming changes</a>
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.583>What's new in 1.583</a> <!--=DATE=--></h3>
<h3><a name=v1.584>What's new in 1.584</a> <!--=DATE=--></h3>
<!--=RC-CHANGES=-->
</div><!--=END=-->
<h3><a name=v1.583>What's new in 1.583</a> (2014/10/01)</h3>
<ul class=image>
<li class='major bug'>
Fixes to multiple security vulnerabilities.
(<a href="https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2014-10-01">security advisory</a>)
<!--=RC-CHANGES=-->
<h3><a name=v1.582>What's new in 1.582</a> (2014/09/28)</h3>
<ul class=image>
<li class=bug>
......
......@@ -5,7 +5,7 @@
<parent>
<artifactId>pom</artifactId>
<groupId>org.jenkins-ci.main</groupId>
<version>1.583-SNAPSHOT</version>
<version>1.584-SNAPSHOT</version>
</parent>
<artifactId>cli</artifactId>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.583-SNAPSHOT</version>
<version>1.584-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......@@ -156,7 +156,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.kohsuke.stapler</groupId>
<artifactId>stapler-adjunct-zeroclipboard</artifactId>
<version>1.1.7-1</version>
<version>1.3.5-1</version>
</dependency>
<dependency>
<groupId>org.kohsuke.stapler</groupId>
......@@ -568,6 +568,12 @@ THE SOFTWARE.
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1-jenkins-1</version>
</dependency>
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
......
......@@ -30,19 +30,19 @@ import hudson.model.Saveable;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.SaveableListener;
import hudson.model.Descriptor.FormException;
import org.kohsuke.stapler.MetaClass;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.File;
import java.net.URL;
import net.sf.json.JSONObject;
import com.thoughtworks.xstream.XStream;
import java.net.URI;
import java.net.URISyntaxException;
import org.kohsuke.stapler.HttpResponses;
/**
* Base class of Hudson plugin.
......@@ -204,15 +204,13 @@ public abstract class Plugin implements Saveable {
public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
String path = req.getRestOfPath();
if (path.startsWith("/META-INF/") || path.startsWith("/WEB-INF/")) {
throw HttpResponses.notFound();
}
if(path.length()==0)
path = "/";
if(path.indexOf("..")!=-1 || path.length()<1) {
// don't serve anything other than files in the sub directory.
rsp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
// Stapler routes requests like the "/static/.../foo/bar/zot" to be treated like "/foo/bar/zot"
// and this is used to serve long expiration header, by using Jenkins.VERSION_HASH as "..."
// to create unique URLs. Recognize that and set a long expiration header.
......@@ -222,7 +220,11 @@ public abstract class Plugin implements Saveable {
long expires = staticLink ? TimeUnit2.DAYS.toMillis(365) : -1;
// use serveLocalizedFile to support automatic locale selection
rsp.serveLocalizedFile(req, new URL(wrapper.baseResourceURL,'.'+path),expires);
try {
rsp.serveLocalizedFile(req, wrapper.baseResourceURL.toURI().resolve(new URI(null, '.' + path, null)).toURL(), expires);
} catch (URISyntaxException x) {
throw new IOException(x);
}
}
//
......
......@@ -37,6 +37,7 @@ import hudson.remoting.ChannelProperty;
import hudson.security.CliAuthenticator;
import hudson.security.SecurityRealm;
import org.acegisecurity.Authentication;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.apache.commons.discovery.ResourceClassIterator;
......@@ -63,6 +64,7 @@ import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -240,6 +242,13 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
// signals an error without stack trace
stderr.println(e.getMessage());
return -1;
} catch (BadCredentialsException e) {
// to the caller, we can't reveal whether the user didn't exist or the password didn't match.
// do that to the server log instead
String id = UUID.randomUUID().toString();
LOGGER.log(Level.INFO, "CLI login attempt failed: "+id, e);
stderr.println("Bad Credentials. Search the server log for "+id+" for more details.");
return -1;
} catch (Exception e) {
e.printStackTrace(stderr);
return -1;
......
......@@ -44,6 +44,7 @@ import hudson.util.AtomicFileWriter;
import hudson.util.IOUtils;
import jenkins.model.DirectlyModifiableTopLevelItemGroup;
import jenkins.model.Jenkins;
import org.acegisecurity.Authentication;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FileSet;
import org.kohsuke.stapler.WebMethod;
......@@ -211,7 +212,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
* Not all the Items need to support this operation, but if you decide to do so,
* you can use this method.
*/
protected void renameTo(String newName) throws IOException {
protected void renameTo(final String newName) throws IOException {
// always synchronize from bigger objects first
final ItemGroup parent = getParent();
synchronized (parent) {
......@@ -224,13 +225,28 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
if (this.name.equals(newName))
return;
Item existing = parent.getItem(newName);
if (existing != null && existing!=this)
// the look up is case insensitive, so we need "existing!=this"
// to allow people to rename "Foo" to "foo", for example.
// see http://www.nabble.com/error-on-renaming-project-tt18061629.html
throw new IllegalArgumentException("Job " + newName
+ " already exists");
// the test to see if the project already exists or not needs to be done in escalated privilege
// to avoid overwriting
ACL.impersonate(ACL.SYSTEM,new Callable<Void,IOException>() {
final Authentication user = Jenkins.getAuthentication();
@Override
public Void call() throws IOException {
Item existing = parent.getItem(newName);
if (existing != null && existing!=AbstractItem.this) {
if (existing.getACL().hasPermission(user,Item.DISCOVER))
// the look up is case insensitive, so we need "existing!=this"
// to allow people to rename "Foo" to "foo", for example.
// see http://www.nabble.com/error-on-renaming-project-tt18061629.html
throw new IllegalArgumentException("Job " + newName + " already exists");
else {
// can't think of any real way to hide this, but at least the error message could be vague.
throw new IOException("Unable to rename to " + newName);
}
}
return null;
}
});
String oldName = this.name;
String oldFullName = getFullName();
......@@ -633,7 +649,13 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
}
// try to reflect the changes by reloading
new XmlFile(Items.XSTREAM, out.getTemporaryFile()).unmarshal(this);
Object o = new XmlFile(Items.XSTREAM, out.getTemporaryFile()).unmarshal(this);
if (o!=this) {
// ensure that we've got the same job type. extending this code to support updating
// to different job type requires destroying & creating a new job type
throw new IOException("Expecting "+this.getClass()+" but got "+o.getClass()+" instead");
}
Items.whileUpdatingByXml(new Callable<Void,IOException>() {
@Override public Void call() throws IOException {
onLoad(getParent(), getRootDir().getName());
......
......@@ -39,7 +39,9 @@ import java.io.UnsupportedEncodingException;
import javax.servlet.ServletException;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemHeaders;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.util.FileItemHeadersImpl;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
......@@ -307,5 +309,14 @@ public class FileParameterValue extends ParameterValue {
public OutputStream getOutputStream() throws IOException {
return new FileOutputStream(file);
}
@Override
public FileItemHeaders getHeaders() {
return new FileItemHeadersImpl();
}
@Override
public void setHeaders(FileItemHeaders headers) {
}
}
}
......@@ -28,6 +28,8 @@ import hudson.remoting.PingThread;
import hudson.remoting.Channel.Mode;
import hudson.util.ChunkedOutputStream;
import hudson.util.ChunkedInputStream;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
......@@ -36,6 +38,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -78,9 +82,14 @@ abstract public class FullDuplexHttpChannel {
out.write("Starting HTTP duplex channel".getBytes());
out.flush();
// wait until we have the other channel
while(upload==null)
wait();
{// wait until we have the other channel
long end = System.currentTimeMillis() + CONNECTION_TIMEOUT;
while (upload == null && System.currentTimeMillis()<end)
wait(1000);
if (upload==null)
throw new IOException("HTTP full-duplex channel timeout: "+uuid);
}
try {
channel = new Channel("HTTP full-duplex channel " + uuid,
......@@ -144,5 +153,12 @@ abstract public class FullDuplexHttpChannel {
/**
* Set to true if the servlet container doesn't support chunked encoding.
*/
@Restricted(NoExternalUse.class)
public static boolean DIY_CHUNKING = Boolean.getBoolean("hudson.diyChunking");
/**
* Controls the time out of waiting for the 2nd HTTP request to arrive.
*/
@Restricted(NoExternalUse.class)
public static long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(15);
}
......@@ -24,6 +24,7 @@
package hudson.security;
import javax.annotation.Nonnull;
import hudson.remoting.Callable;
import jenkins.security.NonSerializableSecurityContext;
import jenkins.model.Jenkins;
import org.acegisecurity.AccessDeniedException;
......@@ -146,4 +147,19 @@ public abstract class ACL {
}
}
/**
* Safer variant of {@link #impersonate(Authentication)} that does not require a finally-block.
* @param auth authentication, such as {@link #SYSTEM}
* @param body an action to run with this alternate authentication in effect
* @since TODO
*/
public static <V,T extends Exception> V impersonate(Authentication auth, Callable<V,T> body) throws T {
SecurityContext old = impersonate(auth);
try {
return body.call();
} finally {
SecurityContextHolder.setContext(old);
}
}
}
......@@ -11,11 +11,15 @@ import org.kohsuke.stapler.StaplerRequest;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.servlet.ServletContext;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import static hudson.Util.fixNull;
/**
* Stores the location of Jenkins (e-mail address and the HTTP URL.)
*
......@@ -63,6 +67,8 @@ public class JenkinsLocationConfiguration extends GlobalConfiguration {
} else {
super.load();
}
updateSecureSessionFlag();
}
public String getAdminAddress() {
......@@ -91,6 +97,37 @@ public class JenkinsLocationConfiguration extends GlobalConfiguration {
url += '/';
this.jenkinsUrl = url;
save();
updateSecureSessionFlag();
}
/**
* If the Jenkins URL starts from "https", force the secure session flag
*
* @see <a href="https://www.owasp.org/index.php/SecureFlag">discussion of this topic in OWASP</a>
*/
private void updateSecureSessionFlag() {
try {
ServletContext context = Jenkins.getInstance().servletContext;
Method m;
try {
m = context.getClass().getMethod("getSessionCookieConfig");
} catch (NoSuchMethodException x) { // 3.0+
LOGGER.log(Level.FINE, "Failed to set secure cookie flag", x);
return;
}
Object sessionCookieConfig = m.invoke(context);
// not exposing session cookie to JavaScript to mitigate damage caused by XSS
Class scc = Class.forName("javax.servlet.SessionCookieConfig");
Method setHttpOnly = scc.getMethod("setHttpOnly",boolean.class);
setHttpOnly.invoke(sessionCookieConfig,true);
Method setSecure = scc.getMethod("setSecure",boolean.class);
boolean v = fixNull(jenkinsUrl).startsWith("https");
setSecure.invoke(sessionCookieConfig,v);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Failed to set secure cookie flag", e);
}
}
@Override
......
......@@ -29,7 +29,16 @@ THE SOFTWARE.
<l:icon class="icon-monitor icon-xlg"/>
${%title(it.displayName)}
</h1>
<j:set var="type" value="${request.getParameter('type') ?: 'min'}" />
<j:set var="type" value="${request.getParameter('type')}" />
<j:choose>
<j:when test="${type == 'sec10'}" />
<j:when test="${type == 'min'}" />
<j:when test="${type == 'hour'}" />
<j:otherwise>
<j:set var="type" value="min" />
</j:otherwise>
</j:choose>
<div>
${%Timespan}:
<j:choose>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<f:entry title="${it.name}" description="${it.formattedDescription}">
<div name="parameter">
<input type="hidden" name="name" value="${it.name}" />
<f:password name="value" value="${it.defaultValue}" />
<f:password name="value" value="${it.defaultValueAsSecret}" />
</div>
</f:entry>
</j:jelly>
\ No newline at end of file
.copy-button {
display: inline-block;
}
.copy-button BUTTON {
background: url('clipboard.png') center center no-repeat;
width: 20px;
......
......@@ -5,8 +5,7 @@ Behaviour.specify("span.copy-button", 'copyButton', 0, function(e) {
var id = "copy-button"+(iota++);
btn.id = id;
var clip = new ZeroClipboard.Client();
clip.setText(e.getAttribute("text"));
var clip = new ZeroClipboard(e);
makeButton(btn);
clip.setHandCursor(true);
......@@ -14,24 +13,24 @@ Behaviour.specify("span.copy-button", 'copyButton', 0, function(e) {
if (container) {
container = $(e).up(container);
container.style.position = "relative";
clip.glue(id,container);
} else {
clip.glue(id);
}
clip.addEventListener('onComplete',function() {
clip.on('datarequested',function() {
clip.setText(e.getAttribute("text"));
});
clip.on('complete',function() {
notificationBar.show(e.getAttribute("message"));
});
clip.addEventListener('onMouseOver',function() {
clip.on('mouseOver',function() {
$(id).addClassName('yui-button-hover')
});
clip.addEventListener('onMouseOut',function() {
clip.on('mouseOut',function() {
$(id).removeClassName('yui-button-hover')
});
clip.addEventListener('onMouseDown',function() {
clip.on('mouseDown',function() {
$(id).addClassName('yui-button-active')
});
clip.addEventListener('onMouseUp',function() {
clip.on('mouseUp',function() {
$(id).removeClassName('yui-button-active')
});
});
jenkins (1.583) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
-- Kohsuke Kawaguchi <kk@kohsuke.org> Wed, 01 Oct 2014 12:04:09 -0700
jenkins (1.583) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
-- Kohsuke Kawaguchi <kk@kohsuke.org> Wed, 01 Oct 2014 10:58:54 -0700
jenkins (1.582) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
......
......@@ -11,7 +11,7 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<name>Jenkins plugin POM</name>
<version>1.583-SNAPSHOT</version>
<version>1.584-SNAPSHOT</version>
<packaging>pom</packaging>
<!--
......@@ -40,19 +40,19 @@
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-war</artifactId>
<type>war</type>
<version>1.583-SNAPSHOT</version>
<version>1.584-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-core</artifactId>
<version>1.583-SNAPSHOT</version>
<version>1.584-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness</artifactId>
<version>1.583-SNAPSHOT</version>
<version>1.584-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<!--
......
......@@ -33,7 +33,7 @@ THE SOFTWARE.
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.583-SNAPSHOT</version>
<version>1.584-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Jenkins main module</name>
......@@ -173,7 +173,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>remoting</artifactId>
<version>2.45</version>
<version>2.46</version>
</dependency>
<dependency>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<artifactId>pom</artifactId>
<groupId>org.jenkins-ci.main</groupId>
<version>1.583-SNAPSHOT</version>
<version>1.584-SNAPSHOT</version>
</parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness</artifactId>
......
......@@ -33,6 +33,10 @@ import hudson.tasks.BuildStepMonitor;
import hudson.tasks.BuildTrigger
import hudson.tasks.Publisher
import hudson.tasks.Recorder;
import com.gargoylesoftware.htmlunit.html.HtmlPage
import hudson.maven.MavenModuleSet;
import hudson.security.*;
import hudson.tasks.BuildTrigger;
import hudson.tasks.Shell;
import hudson.scm.NullSCM;
import hudson.scm.SCM
......@@ -49,7 +53,7 @@ import hudson.util.StreamTaskListener;
import hudson.util.OneShotEvent
import jenkins.model.Jenkins;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.context.SecurityContextHolder
import org.jvnet.hudson.test.HudsonTestCase
import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.MemoryAssert
......@@ -524,4 +528,70 @@ public class AbstractProjectTest extends HudsonTestCase {
done.signal()
}
public void testRenameToPrivileged() {
def secret = jenkins.createProject(FreeStyleProject.class,"secret");
def regular = jenkins.createProject(FreeStyleProject.class,"regular")
jenkins.securityRealm = createDummySecurityRealm();
def auth = new ProjectMatrixAuthorizationStrategy();
jenkins.authorizationStrategy = auth;
auth.add(Jenkins.ADMINISTER, "alice");
auth.add(Jenkins.READ, "bob");
// bob the regular user can only see regular jobs
regular.addProperty(new AuthorizationMatrixProperty([(Job.READ) : ["bob"] as Set]));
def wc = createWebClient()
wc.login("bob")
wc.executeOnServer {
assert jenkins.getItem("secret")==null;
try {
regular.renameTo("secret")
fail("rename as an overwrite should have failed");
} catch (Exception e) {
// expected rename to fail in some non-descriptive generic way
e.printStackTrace()
}
}
// those two jobs should still be there
assert jenkins.getItem("regular")!=null;
assert jenkins.getItem("secret")!=null;
}
/**
* Trying to POST to config.xml by a different job type should fail.
*/
public void testConfigDotXmlSubmissionToDifferentType() {
jenkins.crumbIssuer = null
def p = createFreeStyleProject()
HttpURLConnection con = postConfigDotXml(p, "<maven2-moduleset />")
// 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, "<project />")
assert con.responseCode == 200
}
private HttpURLConnection postConfigDotXml(FreeStyleProject p, String xml) {
HttpURLConnection con = new URL(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
}
}
/*
* The MIT License
*
* Copyright 2014 Jesse Glick.
*
* 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;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.junit.Rule;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
public class PluginTest {
@Rule public JenkinsRule r = new JenkinsRule();
@Issue("SECURITY-131") // TODO test-annotations 1.2+: @Issue({"SECURITY-131", "SECURITY-155"})
@Test public void doDynamic() throws Exception {
r.createWebClient().goTo("plugin/credentials/images/24x24/credentials.png", "image/png");
/* Collapsed somewhere before it winds up in restOfPath:
r.createWebClient().assertFails("plugin/credentials/images/../images/24x24/credentials.png", HttpServletResponse.SC_BAD_REQUEST);
*/
r.createWebClient().assertFails("plugin/credentials/images/%2E%2E/images/24x24/credentials.png", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // IAE from TokenList.<init>
r.createWebClient().assertFails("plugin/credentials/images/%252E%252E/images/24x24/credentials.png", HttpServletResponse.SC_NOT_FOUND); // SECURITY-131
r.createWebClient().assertFails("plugin/credentials/images/%25252E%25252E/images/24x24/credentials.png", HttpServletResponse.SC_NOT_FOUND); // just checking
// SECURITY-155:
r.createWebClient().assertFails("plugin/credentials/WEB-INF/licenses.xml", HttpServletResponse.SC_NOT_FOUND);
r.createWebClient().assertFails("plugin/credentials/META-INF/MANIFEST.MF", HttpServletResponse.SC_NOT_FOUND);
}
}
......@@ -7,11 +7,15 @@ import hudson.cli.LoginCommand;
import hudson.cli.LogoutCommand;
import jenkins.model.Jenkins;
import org.acegisecurity.Authentication;
import org.apache.commons.io.input.NullInputStream;
import org.junit.Assert;
import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.TestExtension;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
/**
* @author Kohsuke Kawaguchi
*/
......@@ -36,6 +40,17 @@ public class CliAuthenticationTest extends HudsonTestCase {
}
}
private String commandAndOutput(String... args) throws Exception {
CLI cli = new CLI(getURL());
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cli.execute(Arrays.asList(args), new NullInputStream(0), baos, baos);
return baos.toString();
} finally {
cli.close();
}
}
@TestExtension
public static class TestCommand extends CLICommand {
@Override
......@@ -76,4 +91,20 @@ public class CliAuthenticationTest extends HudsonTestCase {
successfulCommand("logout");
successfulCommand("anonymous"); // now we should run as anonymous
}
/**
* Login failure shouldn't reveal information about the existence of user
*/
public void testSecurity110() throws Exception {
HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false,false,null);
jenkins.setSecurityRealm(realm);
jenkins.setAuthorizationStrategy(new FullControlOnceLoggedInAuthorizationStrategy());
realm.createAccount("alice","alice");
String out1 = commandAndOutput("help", "--username", "alice", "--password", "bogus");
String out2 = commandAndOutput("help", "--username", "bob", "--password", "bogus");
assertTrue(out1.contains("Bad Credentials. Search the server log for"));
assertTrue(out2.contains("Bad Credentials. Search the server log for"));
}
}
......@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.583-SNAPSHOT</version>
<version>1.584-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册