/*
* The MIT License
*
* Copyright (c) 2004-2011, Yahoo!, 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.model;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.maven.MavenModuleSet;
import hudson.maven.MavenModuleSetBuild;
import hudson.model.Failure;
import hudson.model.RestartListener;
import hudson.model.RootAction;
import hudson.model.UnprotectedRootAction;
import hudson.model.User;
import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
import hudson.security.HudsonPrivateSecurityRealm;
import hudson.util.HttpResponses;
import hudson.model.FreeStyleProject;
import hudson.security.GlobalMatrixAuthorizationStrategy;
import hudson.security.LegacySecurityRealm;
import hudson.security.Permission;
import hudson.slaves.ComputerListener;
import hudson.slaves.DumbSlave;
import hudson.slaves.OfflineCause;
import hudson.util.FormValidation;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.ExtractResourceSCM;
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.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author kingfai
*
*/
public class JenkinsTest {
@Rule public JenkinsRule j = new JenkinsRule();
@Test
public void testIsDisplayNameUniqueTrue() throws Exception {
final String curJobName = "curJobName";
final String jobName = "jobName";
FreeStyleProject curProject = j.createFreeStyleProject(curJobName);
curProject.setDisplayName("currentProjectDisplayName");
FreeStyleProject p = j.createFreeStyleProject(jobName);
p.setDisplayName("displayName");
Jenkins jenkins = Jenkins.getInstance();
assertTrue(jenkins.isDisplayNameUnique("displayName1", curJobName));
assertTrue(jenkins.isDisplayNameUnique(jobName, curJobName));
}
@Test
public void testIsDisplayNameUniqueFalse() throws Exception {
final String curJobName = "curJobName";
final String jobName = "jobName";
final String displayName = "displayName";
FreeStyleProject curProject = j.createFreeStyleProject(curJobName);
curProject.setDisplayName("currentProjectDisplayName");
FreeStyleProject p = j.createFreeStyleProject(jobName);
p.setDisplayName(displayName);
Jenkins jenkins = Jenkins.getInstance();
assertFalse(jenkins.isDisplayNameUnique(displayName, curJobName));
}
@Test
public void testIsDisplayNameUniqueSameAsCurrentJob() throws Exception {
final String curJobName = "curJobName";
final String displayName = "currentProjectDisplayName";
FreeStyleProject curProject = j.createFreeStyleProject(curJobName);
curProject.setDisplayName(displayName);
Jenkins jenkins = Jenkins.getInstance();
// should be true as we don't test against the current job
assertTrue(jenkins.isDisplayNameUnique(displayName, curJobName));
}
@Test
public void testIsNameUniqueTrue() throws Exception {
final String curJobName = "curJobName";
final String jobName = "jobName";
j.createFreeStyleProject(curJobName);
j.createFreeStyleProject(jobName);
Jenkins jenkins = Jenkins.getInstance();
assertTrue(jenkins.isNameUnique("jobName1", curJobName));
}
@Test
public void testIsNameUniqueFalse() throws Exception {
final String curJobName = "curJobName";
final String jobName = "jobName";
j.createFreeStyleProject(curJobName);
j.createFreeStyleProject(jobName);
Jenkins jenkins = Jenkins.getInstance();
assertFalse(jenkins.isNameUnique(jobName, curJobName));
}
@Test
public void testIsNameUniqueSameAsCurrentJob() throws Exception {
final String curJobName = "curJobName";
final String jobName = "jobName";
j.createFreeStyleProject(curJobName);
j.createFreeStyleProject(jobName);
Jenkins jenkins = Jenkins.getInstance();
// true because we don't test against the current job
assertTrue(jenkins.isNameUnique(curJobName, curJobName));
}
@Test
public void testDoCheckDisplayNameUnique() throws Exception {
final String curJobName = "curJobName";
final String jobName = "jobName";
FreeStyleProject curProject = j.createFreeStyleProject(curJobName);
curProject.setDisplayName("currentProjectDisplayName");
FreeStyleProject p = j.createFreeStyleProject(jobName);
p.setDisplayName("displayName");
Jenkins jenkins = Jenkins.getInstance();
FormValidation v = jenkins.doCheckDisplayName("1displayName", curJobName);
assertEquals(FormValidation.ok(), v);
}
@Test
public void testDoCheckDisplayNameSameAsDisplayName() throws Exception {
final String curJobName = "curJobName";
final String jobName = "jobName";
final String displayName = "displayName";
FreeStyleProject curProject = j.createFreeStyleProject(curJobName);
curProject.setDisplayName("currentProjectDisplayName");
FreeStyleProject p = j.createFreeStyleProject(jobName);
p.setDisplayName(displayName);
Jenkins jenkins = Jenkins.getInstance();
FormValidation v = jenkins.doCheckDisplayName(displayName, curJobName);
assertEquals(FormValidation.Kind.WARNING, v.kind);
}
@Test
public void testDoCheckDisplayNameSameAsJobName() throws Exception {
final String curJobName = "curJobName";
final String jobName = "jobName";
final String displayName = "displayName";
FreeStyleProject curProject = j.createFreeStyleProject(curJobName);
curProject.setDisplayName("currentProjectDisplayName");
FreeStyleProject p = j.createFreeStyleProject(jobName);
p.setDisplayName(displayName);
Jenkins jenkins = Jenkins.getInstance();
FormValidation v = jenkins.doCheckDisplayName(jobName, curJobName);
assertEquals(FormValidation.Kind.WARNING, v.kind);
}
@Test
public void testDoCheckViewName_GoodName() throws Exception {
String[] viewNames = new String[] {
"", "Jenkins"
};
Jenkins jenkins = Jenkins.getInstance();
for (String viewName : viewNames) {
FormValidation v = jenkins.doCheckViewName(viewName);
assertEquals(FormValidation.Kind.OK, v.kind);
}
}
@Test
public void testDoCheckViewName_NotGoodName() throws Exception {
String[] viewNames = new String[] {
"Jenkins?", "Jenkins*", "Jenkin/s", "Jenkin\\s", "jenkins%",
"Jenkins!", "Jenkins[]", "Jenkin<>s", "^Jenkins", ".."
};
Jenkins jenkins = Jenkins.getInstance();
for (String viewName : viewNames) {
FormValidation v = jenkins.doCheckViewName(viewName);
assertEquals(FormValidation.Kind.ERROR, v.kind);
}
}
@Test @Issue("JENKINS-12251")
public void testItemFullNameExpansion() throws Exception {
HtmlForm f = j.createWebClient().goTo("configure").getFormByName("config");
f.getInputByName("_.rawBuildsDir").setValueAttribute("${JENKINS_HOME}/test12251_builds/${ITEM_FULL_NAME}");
f.getInputByName("_.rawWorkspaceDir").setValueAttribute("${JENKINS_HOME}/test12251_ws/${ITEM_FULL_NAME}");
j.submit(f);
// build a dummy project
MavenModuleSet m = j.jenkins.createProject(MavenModuleSet.class, "p");
m.setScm(new ExtractResourceSCM(getClass().getResource("/simple-projects.zip")));
MavenModuleSetBuild b = m.scheduleBuild2(0).get();
// make sure these changes are effective
assertTrue(b.getWorkspace().getRemote().contains("test12251_ws"));
assertTrue(b.getRootDir().toString().contains("test12251_builds"));
}
/**
* Makes sure access to "/foobar" for UnprotectedRootAction gets through.
*/
@Test @Issue("JENKINS-14113")
public void testUnprotectedRootAction() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
j.jenkins.setAuthorizationStrategy(new FullControlOnceLoggedInAuthorizationStrategy());
WebClient wc = j.createWebClient();
wc.goTo("foobar");
wc.goTo("foobar/");
wc.goTo("foobar/zot");
// and make sure this fails
wc.assertFails("foobar-zot/", HttpURLConnection.HTTP_INTERNAL_ERROR);
assertEquals(3,j.jenkins.getExtensionList(RootAction.class).get(RootActionImpl.class).count);
}
@Test
public void testDoScript() throws Exception {
j.jenkins.setSecurityRealm(new LegacySecurityRealm());
GlobalMatrixAuthorizationStrategy gmas = new GlobalMatrixAuthorizationStrategy() {
@Override public boolean hasPermission(String sid, Permission p) {
return p == Jenkins.RUN_SCRIPTS ? hasExplicitPermission(sid, p) : super.hasPermission(sid, p);
}
};
gmas.add(Jenkins.ADMINISTER, "alice");
gmas.add(Jenkins.RUN_SCRIPTS, "alice");
gmas.add(Jenkins.READ, "bob");
gmas.add(Jenkins.ADMINISTER, "charlie");
j.jenkins.setAuthorizationStrategy(gmas);
WebClient wc = j.createWebClient();
wc.login("alice");
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));
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));
assertEquals("you", System.getProperty("huck"));
wc.login("bob");
wc.assertFails("script", HttpURLConnection.HTTP_FORBIDDEN);
wc.login("charlie");
wc.assertFails("script", HttpURLConnection.HTTP_FORBIDDEN);
}
@Test
public void testDoEval() throws Exception {
j.jenkins.setSecurityRealm(new LegacySecurityRealm());
GlobalMatrixAuthorizationStrategy gmas = new GlobalMatrixAuthorizationStrategy() {
@Override public boolean hasPermission(String sid, Permission p) {
return p == Jenkins.RUN_SCRIPTS ? hasExplicitPermission(sid, p) : super.hasPermission(sid, p);
}
};
gmas.add(Jenkins.ADMINISTER, "alice");
gmas.add(Jenkins.RUN_SCRIPTS, "alice");
gmas.add(Jenkins.READ, "bob");
gmas.add(Jenkins.ADMINISTER, "charlie");
j.jenkins.setAuthorizationStrategy(gmas);
WebClient wc = j.createWebClient();
wc.login("alice");
wc.assertFails("eval", HttpURLConnection.HTTP_BAD_METHOD);
assertEquals("3", eval(wc));
wc.login("bob");
try {
eval(wc);
fail("bob has only READ");
} catch (FailingHttpStatusCodeException e) {
assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getStatusCode());
}
wc.login("charlie");
try {
eval(wc);
fail("charlie has ADMINISTER but not RUN_SCRIPTS");
} catch (FailingHttpStatusCodeException e) {
assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getStatusCode());
}
}
private String eval(WebClient wc) throws Exception {
WebRequest req = new WebRequest(wc.createCrumbedUrl("eval"), HttpMethod.POST);
req.setEncodingType(null);
req.setRequestBody("<j:jelly xmlns:j='jelly:core'>${1+2}</j:jelly>");
return wc.getPage(req).getWebResponse().getContentAsString();
}
@TestExtension("testUnprotectedRootAction")
public static class RootActionImpl implements UnprotectedRootAction {
private int count;
public String getIconFileName() {
return null;
}
public String getDisplayName() {
return null;
}
public String getUrlName() {
return "foobar";
}
public HttpResponse doDynamic() {
assertTrue(Jenkins.getInstance().getAuthentication().getName().equals("anonymous"));
count++;
return HttpResponses.html("OK");
}
}
@TestExtension("testUnprotectedRootAction")
public static class ProtectedRootActionImpl implements RootAction {
public String getIconFileName() {
return null;
}
public String getDisplayName() {
return null;
}
public String getUrlName() {
return "foobar-zot";
}
public HttpResponse doDynamic() {
throw new AssertionError();
}
}
@Test @Issue("JENKINS-20866")
public void testErrorPageShouldBeAnonymousAccessible() throws Exception {
HudsonPrivateSecurityRealm s = new HudsonPrivateSecurityRealm(false, false, null);
User alice = s.createAccount("alice", "alice");
j.jenkins.setSecurityRealm(s);
GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy();
j.jenkins.setAuthorizationStrategy(auth);
// no anonymous read access
assertTrue(!Jenkins.getInstance().getACL().hasPermission(Jenkins.ANONYMOUS,Jenkins.READ));
WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("error/reportError");
assertEquals(p.asText(), 400, p.getWebResponse().getStatusCode()); // not 403 forbidden
assertTrue(p.getWebResponse().getContentAsString().contains("My car is black"));
}
@TestExtension("testErrorPageShouldBeAnonymousAccessible")
public static class ReportError implements UnprotectedRootAction {
public String getIconFileName() {
return null;
}
public String getDisplayName() {
return null;
}
public String getUrlName() {
return "error";
}
public HttpResponse doReportError() {
return new Failure("My car is black");
}
}
@Test @Issue("JENKINS-23551")
public void testComputerListenerNotifiedOnRestart() {
// Simulate restart calling listeners
for (RestartListener listener : RestartListener.all())
listener.onRestart();
ArgumentCaptor<OfflineCause> captor = ArgumentCaptor.forClass(OfflineCause.class);
Mockito.verify(listenerMock).onOffline(Mockito.eq(j.jenkins.toComputer()), captor.capture());
assertTrue(captor.getValue().toString().contains("restart"));
}
@TestExtension(value = "testComputerListenerNotifiedOnRestart")
public static final ComputerListener listenerMock = Mockito.mock(ComputerListener.class);
@Test
public void runScriptOnOfflineComputer() throws Exception {
DumbSlave slave = j.createSlave(true);
j.disconnectSlave(slave);
URL url = new URL(j.getURL(), "computer/" + slave.getNodeName() + "/scriptText?script=println(42)");
WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
WebRequest req = new WebRequest(url, HttpMethod.POST);
Page page = wc.getPage(wc.addCrumb(req));
WebResponse rsp = page.getWebResponse();
assertThat(rsp.getContentAsString(), containsString("Node is offline"));
assertThat(rsp.getStatusCode(), equalTo(404));
}
}