Skip to content

Commit

Permalink
[SECURITY-2831]
Browse files Browse the repository at this point in the history
  • Loading branch information
rsandell committed Oct 12, 2022
1 parent f582e39 commit dfb723c
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 4 deletions.
19 changes: 15 additions & 4 deletions src/main/java/hudson/plugins/mercurial/MercurialStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.springframework.security.core.Authentication;

import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_OK;
Expand Down Expand Up @@ -108,20 +109,22 @@ public HttpResponse doNotifyCommit(@QueryParameter(required=true) final String u
String origin = SCMEvent.originOf(Stapler.getCurrentRequest());
// run in high privilege to see all the projects anonymous users don't see.
// this is safe because we only initiate polling.
// But we shouldn't disclose the item names to users that is not supposed to see them
final Authentication origAuth = Jenkins.getAuthentication2();
try (ACLContext context = ACL.as(ACL.SYSTEM)) {
if (StringUtils.isNotBlank(branch) && StringUtils.isNotBlank(changesetId)) {
SCMHeadEvent.fireNow(new MercurialSCMHeadEvent(
SCMEvent.Type.UPDATED, new MercurialCommitPayload(new URI(url), branch, changesetId),
origin));
return HttpResponses.ok();
}
return handleNotifyCommit(origin, new URI(url));
return handleNotifyCommit(origin, new URI(url), origAuth);
} catch ( URISyntaxException ex ) {
throw HttpResponses.error(SC_BAD_REQUEST, ex);
}
}

private HttpResponse handleNotifyCommit(String origin, URI url) throws ServletException, IOException {
private HttpResponse handleNotifyCommit(String origin, URI url, final Authentication origAuth) throws ServletException, IOException {
final List<Item> projects = Lists.newArrayList();
boolean scmFound = false,
urlFound = false;
Expand Down Expand Up @@ -203,11 +206,19 @@ public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object nod
rsp.setStatus(SC_OK);
rsp.setContentType("text/plain");
for (Item p : projects) {
rsp.addHeader("Triggered", p.getAbsoluteUrl());
if (p.hasPermission2(origAuth, Item.READ)) {
rsp.addHeader("Triggered", p.getAbsoluteUrl());
} else {
rsp.addHeader("Triggered", "Something");
}
}
PrintWriter w = rsp.getWriter();
for (Item p : projects) {
w.println("Scheduled polling of " + p.getFullName());
if (p.hasPermission2(origAuth, Item.READ)) {
w.println("Scheduled polling of " + p.getFullName());
} else {
w.println("Scheduled polling of a job");
}
}
if (msg!=null)
w.println(msg);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package hudson.plugins.mercurial;

import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import hudson.FilePath;
import hudson.model.FreeStyleProject;
import hudson.model.Item;
import hudson.model.Slave;
import hudson.triggers.SCMTrigger;
import jenkins.model.Jenkins;
import org.hamcrest.Matcher;
import org.jenkinsci.test.acceptance.docker.DockerClassRule;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;
import org.jvnet.hudson.test.SleepBuilder;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
import static org.hamcrest.collection.ArrayMatching.hasItemInArray;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.StringContains.containsString;
import static org.hamcrest.core.StringEndsWith.endsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

public class MercurialStatusPermissionTest {

@Rule public JenkinsRule j = new JenkinsRule();
@Rule public MercurialRule m = new MercurialRule(j);
@ClassRule
public static DockerClassRule<MercurialContainer> docker = new DockerClassRule<>(MercurialContainer.class);

private final String[] names = new String[]{"one1", "two2", "three3"};
private final String carls = names[1];
private String source;

@Before
public void setup() throws Exception {
m.hg("version"); // test environment needs to be able to run Mercurial
MercurialContainer container = docker.create();
Slave slave = container.createSlave(j);
m.withNode(slave);
MercurialInstallation inst = container.createInstallation(j, MercurialContainer.Version.HG5, false, false, false, "", slave);
assertNotNull(inst);
m.withInstallation(inst);
FilePath sampleRepo = slave.getRootPath().child("sampleRepo");
sampleRepo.mkdirs();
m.hg(sampleRepo, "init");
sampleRepo.child("a").write("a", "UTF-8");
m.hg(sampleRepo, "commit", "--addremove", "--message=a-file");

source = "ssh://test@" + container.ipBound(22) + ":" + container.port(22) + "/" + sampleRepo;

FreeStyleProject p = j.createFreeStyleProject(names[0]);
p.setScm(new MercurialSCM(
inst.getName(),
source,
null, null, null, null, false));
p.addTrigger(new SCMTrigger(""));
p.getBuildersList().add(new SleepBuilder(1000));
p = j.createFreeStyleProject(names[1]);
FreeStyleProject carls = p;
p.setScm(new MercurialSCM(
inst.getName(),
source,
null, null, null, null, false));
p.addTrigger(new SCMTrigger(""));
p.getBuildersList().add(new SleepBuilder(1000));
p = j.createFreeStyleProject(names[2]);
p.setScm(new MercurialSCM(
inst.getName(),
source,
null, null, null, null, false));
p.addTrigger(new SCMTrigger(""));
p.getBuildersList().add(new SleepBuilder(1000));

j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
.grant(Item.READ).everywhere().to("bob")
.grant(Item.READ).onItems(carls).to("carl")
.grant(Jenkins.ADMINISTER).everywhere().to("alice")
.grant(Jenkins.READ).onRoot().toAuthenticated());
}

@Test
public void testTriggeredWithAnonymous() throws Exception {
final Page page = j.createWebClient().goTo("mercurial/notifyCommit?url=" + source, "text/plain");
final WebResponse response = page.getWebResponse();
assertEquals(200, response.getStatusCode());
final List<NameValuePair> headers = response.getResponseHeaders();
final List<NameValuePair> triggered = headers.stream().filter(nvp -> nvp.getName().equals("Triggered")).collect(Collectors.toList());
assertEquals("No headers!", 3, triggered.size());
List<String> headerValues = triggered.stream().map(NameValuePair::getValue).collect(Collectors.toList());
final String content = response.getContentAsString();
assertThat(content, containsString("Scheduled polling of a job"));
assertThat(headerValues.toArray(new String[headerValues.size()]), hasItemInArray(containsString("Something")));

List<Matcher<? super String>> testHeaders = new ArrayList<>();
for (String name : names) {
assertThat(content, not(containsString(name)));
testHeaders.add(not(endsWith(name + "/")));
}

assertThat(headerValues, containsInAnyOrder(testHeaders));
}

@Test
public void testTriggeredWithAllReadable() throws Exception {
final Page page = j.createWebClient().login("bob").goTo("mercurial/notifyCommit?url=" + source, "text/plain");
final WebResponse response = page.getWebResponse();
assertEquals(200, response.getStatusCode());
final List<NameValuePair> headers = response.getResponseHeaders();
final List<NameValuePair> triggered = headers.stream().filter(nvp -> nvp.getName().equals("Triggered")).collect(Collectors.toList());
assertEquals("No headers!", 3, triggered.size());
List<String> headerValues = triggered.stream().map(NameValuePair::getValue).collect(Collectors.toList());
final String content = response.getContentAsString();
assertThat(content, containsString("Scheduled polling of"));
assertThat(content, not(containsString("Scheduled polling of a job")));
assertThat(headerValues.toArray(new String[headerValues.size()]), not(hasItemInArray(containsString("Something"))));

List<Matcher<? super String>> testHeaders = new ArrayList<>();
for (String name : names) {
assertThat(content, containsString(name));
testHeaders.add(endsWith(name + "/"));
}

assertThat(headerValues, containsInAnyOrder(testHeaders));
}

@Test
public void testTriggeredWithOneReadable() throws Exception {
final Page page = j.createWebClient().login("carl").goTo("mercurial/notifyCommit?url=" + source, "text/plain");
final WebResponse response = page.getWebResponse();
assertEquals(200, response.getStatusCode());
final List<NameValuePair> headers = response.getResponseHeaders();
final List<NameValuePair> triggered = headers.stream().filter(nvp -> nvp.getName().equals("Triggered")).collect(Collectors.toList());
assertEquals("No headers!", 3, triggered.size());
List<String> headerValues = triggered.stream().map(NameValuePair::getValue).collect(Collectors.toList());
final String content = response.getContentAsString();
assertThat(content, containsString("Scheduled polling of"));
assertThat(content, containsString("Scheduled polling of a job"));
assertThat(headerValues.toArray(new String[headerValues.size()]), hasItemInArray(containsString("Something")));

List<Matcher<? super String>> testHeaders = new ArrayList<>();
for (String name : names) {
if (name.equals(carls)) {
assertThat(content, containsString(name));
testHeaders.add(endsWith(name + "/"));
} else {
assertThat(content, not(containsString(name)));
testHeaders.add(not(endsWith(name + "/")));
}
}

assertThat(headerValues, containsInAnyOrder(testHeaders));
}
}

0 comments on commit dfb723c

Please sign in to comment.