/**
* Copyright (c) 2008-2010 Yahoo! Inc.
* All rights reserved.
* The copyrights to the contents of this file are licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
package hudson.security.csrf;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import net.sf.json.JSONObject;
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.recipes.PresetData;
import java.net.HttpURLConnection;
import static org.junit.Assert.*;
/**
*
* @author dty
*/
public class DefaultCrumbIssuerTest {
@Rule public JenkinsRule r = new JenkinsRule();
@Before public void setIssuer() {
r.jenkins.setCrumbIssuer(new DefaultCrumbIssuer(false));
}
private static final String[] testData = {
"10.2.3.1",
"10.2.3.1,10.20.30.40",
"10.2.3.1,10.20.30.41",
"10.2.3.3,10.20.30.40,10.20.30.41"
};
private static final String HEADER_NAME = "X-Forwarded-For";
@Issue("JENKINS-3854")
@Test public void clientIPFromHeader() throws Exception {
WebClient wc = r.createWebClient();
wc.addRequestHeader(HEADER_NAME, testData[0]);
HtmlPage p = wc.goTo("configure");
r.submit(p.getFormByName("config"));
}
@Issue("JENKINS-3854")
@Test public void headerChange() throws Exception {
WebClient wc = r.createWebClient();
wc.addRequestHeader(HEADER_NAME, testData[0]);
HtmlPage p = wc.goTo("configure");
wc.removeRequestHeader(HEADER_NAME);
try {
// The crumb should no longer match if we remove the proxy info
r.submit(p.getFormByName("config"));
}
catch (FailingHttpStatusCodeException e) {
assertEquals(403,e.getStatusCode());
}
}
@Issue("JENKINS-3854")
@Test public void proxyIPChanged() throws Exception {
WebClient wc = r.createWebClient();
wc.addRequestHeader(HEADER_NAME, testData[1]);
HtmlPage p = wc.goTo("configure");
wc.removeRequestHeader(HEADER_NAME);
wc.addRequestHeader(HEADER_NAME, testData[2]);
// The crumb should be the same even if the proxy IP changes
r.submit(p.getFormByName("config"));
}
@Issue("JENKINS-3854")
@Test public void proxyIPChain() throws Exception {
WebClient wc = r.createWebClient();
wc.addRequestHeader(HEADER_NAME, testData[3]);
HtmlPage p = wc.goTo("configure");
r.submit(p.getFormByName("config"));
}
@Issue("JENKINS-7518")
@Test public void proxyCompatibilityMode() throws Exception {
CrumbIssuer issuer = new DefaultCrumbIssuer(true);
assertNotNull(issuer);
r.jenkins.setCrumbIssuer(issuer);
WebClient wc = r.createWebClient();
wc.addRequestHeader(HEADER_NAME, testData[0]);
HtmlPage p = wc.goTo("configure");
wc.removeRequestHeader(HEADER_NAME);
// The crumb should still match if we remove the proxy info
r.submit(p.getFormByName("config"));
}
@PresetData(PresetData.DataSet.ANONYMOUS_READONLY)
@Test public void apiXml() throws Exception {
WebClient wc = r.createWebClient();
r.assertXPathValue(wc.goToXml("crumbIssuer/api/xml"), "//crumbRequestField", r.jenkins.getCrumbIssuer().getCrumbRequestField());
String text = wc.goTo("crumbIssuer/api/xml?xpath=concat(//crumbRequestField,'=',//crumb)", "text/plain").getWebResponse().getContentAsString();
assertTrue(text, text.matches("\\Q" + r.jenkins.getCrumbIssuer().getCrumbRequestField() + "\\E=[0-9a-f]+"));
text = wc.goTo("crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)", "text/plain").getWebResponse().getContentAsString();
assertTrue(text, text.matches("\\Q" + r.jenkins.getCrumbIssuer().getCrumbRequestField() + "\\E:[0-9a-f]+"));
text = wc.goTo("crumbIssuer/api/xml?xpath=/*/crumbRequestField/text()", "text/plain").getWebResponse().getContentAsString();
assertEquals(r.jenkins.getCrumbIssuer().getCrumbRequestField(), text);
text = wc.goTo("crumbIssuer/api/xml?xpath=/*/crumb/text()", "text/plain").getWebResponse().getContentAsString();
assertTrue(text, text.matches("[0-9a-f]+"));
wc.assertFails("crumbIssuer/api/xml?xpath=concat('hack=\"',//crumb,'\"')", HttpURLConnection.HTTP_FORBIDDEN);
wc.assertFails("crumbIssuer/api/xml?xpath=concat(\"hack='\",//crumb,\"'\")", HttpURLConnection.HTTP_FORBIDDEN);
wc.assertFails("crumbIssuer/api/xml?xpath=concat('{',//crumb,':1}')", HttpURLConnection.HTTP_FORBIDDEN); // 37.5% chance that crumb ~ /[a-f].+/
wc.assertFails("crumbIssuer/api/xml?xpath=concat('hack.',//crumb,'=1')", HttpURLConnection.HTTP_FORBIDDEN); // ditto
r.jenkins.getCrumbIssuer().getDescriptor().setCrumbRequestField("_crumb");
wc.assertFails("crumbIssuer/api/xml?xpath=concat(//crumbRequestField,'=',//crumb)", HttpURLConnection.HTTP_FORBIDDEN); // perhaps interpretable as JS number
}
@PresetData(PresetData.DataSet.ANONYMOUS_READONLY)
@Test public void apiJson() throws Exception {
WebClient wc = r.createWebClient();
String json = wc.goTo("crumbIssuer/api/json", "application/json").getWebResponse().getContentAsString();
JSONObject jsonObject = JSONObject.fromObject(json);
assertEquals(r.jenkins.getCrumbIssuer().getCrumbRequestField(),jsonObject.getString("crumbRequestField"));
assertTrue(jsonObject.getString("crumb").matches("[0-9a-f]+"));
wc.assertFails("crumbIssuer/api/json?jsonp=hack", HttpURLConnection.HTTP_FORBIDDEN);
}
}