Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-beck committed Mar 15, 2021
1 parent fb70998 commit 2490ed5
Show file tree
Hide file tree
Showing 3 changed files with 267 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.synopsys.arc.jenkins.plugins.rolestrategy.RoleType;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.Items;
import hudson.model.User;
import hudson.security.AccessControlled;
Expand Down Expand Up @@ -472,21 +473,41 @@ public AclImpl(RoleType roleType, AccessControlled item) {
/**
* Checks if the sid has the given permission.
* <p>Actually only delegate the check to the {@link RoleMap} instance.</p>
* @param p The sid to check
* @param sid The sid to check
* @param permission The permission to check
* @return True if the sid has the given permission
*/
@SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "As declared in Jenkins API")
@Override
@CheckForNull
protected Boolean hasPermission(Sid p, Permission permission) {
if (RoleMap.this.hasPermission(toString(p), permission, roleType, item)) {
protected Boolean hasPermission(Sid sid, Permission permission) {
if (RoleMap.this.hasPermission(toString(sid), permission, roleType, item)) {
if (item instanceof Item) {
final ItemGroup parent = ((Item)item).getParent();
if (parent instanceof Item && (Item.DISCOVER.equals(permission) || Item.READ.equals(permission)) && shouldCheckParentPermissions()) {
// For READ and DISCOVER permission checks, do the same permission check on the parent
Permission requiredPermissionOnParent = permission == Item.DISCOVER ? Item.DISCOVER : Item.READ;
if (!((Item) parent).hasPermission(requiredPermissionOnParent)) {
return null;
}
}
}
return true;
}
return null;
}
}

private static boolean shouldCheckParentPermissions() {
// TODO Switch to SystemProperties in 2.236+
String propertyName = RoleMap.class.getName() + ".checkParentPermissions";
String value = System.getProperty(propertyName);
if (value == null) {
return true;
}
return Boolean.parseBoolean(value);
}

/**
* A class to walk through all the {@link RoleMap}'s roles and perform an
* action on each one.
Expand Down
174 changes: 174 additions & 0 deletions src/test/java/org/jenkinsci/plugins/rolestrategy/Security2182Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package org.jenkinsci.plugins.rolestrategy;

import com.cloudbees.hudson.plugins.folder.Folder;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.michelin.cio.hudson.plugins.rolestrategy.RoleMap;
import hudson.model.Cause;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.User;
import jenkins.model.Jenkins;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.SleepBuilder;
import org.jvnet.hudson.test.recipes.LocalData;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;

public class Security2182Test {
private static final String BUILD_CONTENT = "Started by user";
private static final String JOB_CONTENT = "Full project name: folder/job";

@Rule
public JenkinsRule j = new JenkinsRule();

@Test
@LocalData
public void testQueuePath() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
Folder folder = j.jenkins.createProject(Folder.class, "folder");
FreeStyleProject job = folder.createProject(FreeStyleProject.class, "job");
job.save();

job.scheduleBuild2(1000, new Cause.UserIdCause("admin"));
Assert.assertEquals(1, Jenkins.get().getQueue().getItems().length);

final JenkinsRule.WebClient webClient = j.createWebClient().withThrowExceptionOnFailingStatusCode(false);
final HtmlPage htmlPage = webClient.goTo("queue/items/0/task/");
final String contentAsString = htmlPage.getWebResponse().getContentAsString();
assertThat(contentAsString, not(containsString(JOB_CONTENT))); // Fails while unfixed
}

@Test
@LocalData
public void testQueueContent() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
Folder folder = j.jenkins.createProject(Folder.class, "folder");
FreeStyleProject job = folder.createProject(FreeStyleProject.class, "job");
job.save();

job.scheduleBuild2(1000, new Cause.UserIdCause("admin"));
Assert.assertEquals(1, Jenkins.get().getQueue().getItems().length);

final JenkinsRule.WebClient webClient = j.createWebClient();
final Page page = webClient.goTo("queue/api/xml/", "application/xml");
final String xml = page.getWebResponse().getContentAsString();
assertThat(xml, not(containsString("job/folder/job/job"))); // Fails while unfixed
}

@Test
@LocalData
public void testExecutorsPath() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
Folder folder = j.jenkins.createProject(Folder.class, "folder");
FreeStyleProject job = folder.createProject(FreeStyleProject.class, "job");
job.getBuildersList().add(new SleepBuilder(100000));
job.save();

final FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserIdCause("admin")).waitForStart();
final int number = build.getExecutor().getNumber();

final JenkinsRule.WebClient webClient = j.createWebClient().withThrowExceptionOnFailingStatusCode(false);
final HtmlPage htmlPage = webClient.goTo("computer/(master)/executors/" + number + "/currentExecutable/");
final String contentAsString = htmlPage.getWebResponse().getContentAsString();
assertThat(contentAsString, not(containsString(BUILD_CONTENT))); // Fails while unfixed
}

@Test
@LocalData
public void testExecutorsContent() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
Folder folder = j.jenkins.createProject(Folder.class, "folder");
FreeStyleProject job = folder.createProject(FreeStyleProject.class, "job");
job.getBuildersList().add(new SleepBuilder(10000));
job.save();

final FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserIdCause("admin")).waitForStart();
final int number = build.getExecutor().getNumber();

final JenkinsRule.WebClient webClient = j.createWebClient();
final Page page = webClient.goTo("computer/(master)/api/xml?depth=1", "application/xml");
final String xml = page.getWebResponse().getContentAsString();
assertThat(xml, not(containsString("job/folder/job/job"))); // Fails while unfixed
}

@Test
@LocalData
public void testWidgets() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
Folder folder = j.jenkins.createProject(Folder.class, "folder");
FreeStyleProject job = folder.createProject(FreeStyleProject.class, "job");
job.getBuildersList().add(new SleepBuilder(100000));
job.save();

job.scheduleBuild2(0, new Cause.UserIdCause("admin")).waitForStart(); // schedule one build now
job.scheduleBuild2(0, new Cause.UserIdCause("admin")); // schedule an additional queue item
Assert.assertEquals(1, Jenkins.get().getQueue().getItems().length); // expect there to be one queue item

final JenkinsRule.WebClient webClient = j.createWebClient().withThrowExceptionOnFailingStatusCode(false);

final HtmlPage htmlPage = webClient.goTo("");
final String contentAsString = htmlPage.getWebResponse().getContentAsString();
assertThat(contentAsString, not(containsString("job/folder/job/job"))); // Fails while unfixed
}

@Test
@LocalData
public void testEscapeHatch() throws Exception {
final String propertyName = RoleMap.class.getName() + ".checkParentPermissions";
try {
System.setProperty(propertyName, "false");


j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
User root = User.getOrCreateByIdOrFullName("admin");
Folder folder = j.jenkins.createProject(Folder.class, "folder");
FreeStyleProject job = folder.createProject(FreeStyleProject.class, "job");
job.getBuildersList().add(new SleepBuilder(100000));
job.save();

job.scheduleBuild2(1000, new Cause.UserIdCause("admin"));
Assert.assertEquals(1, Jenkins.get().getQueue().getItems().length);

final JenkinsRule.WebClient webClient = j.createWebClient().withThrowExceptionOnFailingStatusCode(false);

{ // queue related assertions
final HtmlPage htmlPage = webClient.goTo("queue/items/0/task/");
final String contentAsString = htmlPage.getWebResponse().getContentAsString();
assertThat(contentAsString, containsString(JOB_CONTENT)); // Fails while unfixed

final Page page = webClient.goTo("queue/api/xml/", "application/xml");
final String xml = page.getWebResponse().getContentAsString();
assertThat(xml, containsString("job/folder/job/job")); // Fails while unfixed
}

final FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserIdCause("admin")).waitForStart();
final int number = build.getExecutor().getNumber();
Assert.assertEquals(0, Jenkins.get().getQueue().getItems().length); // collapsed queue items

{ // executor related assertions
final HtmlPage htmlPage = webClient.goTo("computer/(master)/executors/" + number + "/currentExecutable/");
final String contentAsString = htmlPage.getWebResponse().getContentAsString();
assertThat(contentAsString, containsString(BUILD_CONTENT)); // Fails while unfixed

final Page page = webClient.goTo("computer/(master)/api/xml?depth=1", "application/xml");
final String xml = page.getWebResponse().getContentAsString();
assertThat(xml, containsString("job/folder/job/job")); // Fails while unfixed
}

{ // widget related assertions
final HtmlPage htmlPage = webClient.goTo("");
final String contentAsString = htmlPage.getWebResponse().getContentAsString();
assertThat(contentAsString, containsString("job/folder/job/job")); // Fails while unfixed
}

} finally {
System.clearProperty(propertyName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?xml version='1.1' encoding='UTF-8'?>
<hudson>
<disabledAdministrativeMonitors/>
<version>2.279</version>
<installStateName>RUNNING</installStateName>
<numExecutors>2</numExecutors>
<mode>NORMAL</mode>
<useSecurity>true</useSecurity>
<authorizationStrategy class="com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy">
<roleMap type="globalRoles">
<role name="admin" pattern=".*">
<permissions>
<permission>hudson.model.Hudson.Administer</permission>
</permissions>
<assignedSIDs>
<sid>admin</sid>
</assignedSIDs>
</role>
<role name="user" pattern=".*">
<permissions>
<permission>hudson.model.Hudson.Read</permission>
</permissions>
<assignedSIDs>
<sid>anonymous</sid>
</assignedSIDs>
</role>
</roleMap>
<roleMap type="slaveRoles"/>
<roleMap type="projectRoles">
<role name="configurer" pattern="[^/]+/.+">
<permissions>
<permission>hudson.model.Item.Read</permission>
<permission>hudson.model.Item.Configure</permission>
</permissions>
<assignedSIDs>
<sid>anonymous</sid>
</assignedSIDs>
</role>
</roleMap>
</authorizationStrategy>
<securityRealm class="hudson.security.HudsonPrivateSecurityRealm">
<disableSignup>true</disableSignup>
<enableCaptcha>false</enableCaptcha>
</securityRealm>
<disableRememberMe>false</disableRememberMe>
<projectNamingStrategy class="jenkins.model.ProjectNamingStrategy$DefaultProjectNamingStrategy"/>
<workspaceDir>${JENKINS_HOME}/workspace/${ITEM_FULL_NAME}</workspaceDir>
<buildsDir>${ITEM_ROOTDIR}/builds</buildsDir>
<markupFormatter class="hudson.markup.EscapedMarkupFormatter"/>
<jdks/>
<viewsTabBar class="hudson.views.DefaultViewsTabBar"/>
<myViewsTabBar class="hudson.views.DefaultMyViewsTabBar"/>
<clouds/>
<scmCheckoutRetryCount>0</scmCheckoutRetryCount>
<views>
<hudson.model.AllView>
<owner class="hudson" reference="../../.."/>
<name>all</name>
<filterExecutors>false</filterExecutors>
<filterQueue>false</filterQueue>
<properties class="hudson.model.View$PropertyList"/>
</hudson.model.AllView>
</views>
<primaryView>all</primaryView>
<slaveAgentPort>50000</slaveAgentPort>
<label></label>
<nodeProperties/>
<globalNodeProperties/>
</hudson>

0 comments on commit 2490ed5

Please sign in to comment.