diff --git a/pom.xml b/pom.xml index e96dacdb..5b4c8f51 100644 --- a/pom.xml +++ b/pom.xml @@ -83,20 +83,38 @@ io.jenkins.plugins caffeine-api - - org.jenkins-ci.plugins - cloudbees-folder - test - io.jenkins configuration-as-code true + + org.jenkins-ci.plugins + cloudbees-folder + test + io.jenkins.configuration-as-code test-harness test + + com.synopsys.jenkinsci + ownership + 0.13.0 + test + + + org.apache.commons + commons-lang3 + 3.12.0 + test + + + org.jenkins-ci.plugins + authorize-project + 1.4.0 + test + diff --git a/src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/RoleBasedAuthorizationStrategy.java b/src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/RoleBasedAuthorizationStrategy.java index 67281aa5..dbb77ef9 100644 --- a/src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/RoleBasedAuthorizationStrategy.java +++ b/src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/RoleBasedAuthorizationStrategy.java @@ -71,8 +71,6 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -183,6 +181,13 @@ public ACL getACL(@NonNull AbstractItem project) { } + @Override + @NonNull + public ACL getACL(@NonNull Computer computer) { + return agentRoles.newMatchingRoleMap(computer.getName()).getACL(RoleType.Slave, computer) + .newInheritingACL(getRootACL()); + } + @Override @NonNull public ACL getACL(@NonNull Node node) { diff --git a/src/test/java/org/jenkinsci/plugins/rolestrategy/AuthorizeProjectTest.java b/src/test/java/org/jenkinsci/plugins/rolestrategy/AuthorizeProjectTest.java new file mode 100644 index 00000000..537354a6 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/rolestrategy/AuthorizeProjectTest.java @@ -0,0 +1,125 @@ +package org.jenkinsci.plugins.rolestrategy; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import java.io.IOException; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import org.jenkinsci.plugins.authorizeproject.AuthorizeProjectProperty; +import org.jenkinsci.plugins.authorizeproject.ProjectQueueItemAuthenticator; +import org.jenkinsci.plugins.authorizeproject.strategy.TriggeringUsersAuthorizationStrategy; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.JenkinsRule.DummySecurityRealm; +import org.jvnet.hudson.test.recipes.LocalData; +import org.springframework.security.core.Authentication; + +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.model.Cause; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.Slave; +import hudson.model.User; +import hudson.model.Queue.Item; +import hudson.model.Queue.WaitingItem; +import hudson.security.ACL; +import hudson.security.ACLContext; +import hudson.tasks.BuildStepDescriptor; +import hudson.tasks.Builder; +import jenkins.model.Jenkins; +import jenkins.security.QueueItemAuthenticatorConfiguration; + +public class AuthorizeProjectTest +{ + + @Rule + public JenkinsRule j = new JenkinsRule(); + + private Slave n; + private FreeStyleProject p; + private AuthorizationCheckBuilder checker; + + @Before + @LocalData + public void setup() throws Exception { + Authentication a = j.jenkins.getAuthentication2(); + QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new ProjectQueueItemAuthenticator(Collections.emptyMap())); + n = j.createSlave("TestAgent", null, null); + j.waitOnline(n); + p = j.createFreeStyleProject(); + p.setAssignedNode(n); + p.addProperty(new AuthorizeProjectProperty(new TriggeringUsersAuthorizationStrategy())); + checker = new AuthorizationCheckBuilder(); + p.getBuildersList().add(checker); + DummySecurityRealm sr = j.createDummySecurityRealm(); + j.jenkins.setSecurityRealm(sr); + } + + @Test + @LocalData + public void agentBuildPermissionsAllowsToBuildOnAgent() throws Exception { + try (ACLContext c = ACL.as(User.getById("tester", true))) { + p.scheduleBuild2(0, new Cause.UserIdCause()); + } + j.waitUntilNoActivity(); + FreeStyleBuild b = p.getLastBuild(); + assertThat(b, is(not(nullValue()))); + j.assertBuildStatusSuccess(b); + assertThat(checker.userName, is("tester")); + } + + @Test + @LocalData + public void missingAgentBuildPermissionsBlockBuild() throws Exception { + try (ACLContext c = ACL.as(User.getById("reader", true))) { + p.scheduleBuild2(0, new Cause.UserIdCause()); + } + TimeUnit.SECONDS.sleep(15); + Item qi = p.getQueueItem(); + assertThat(qi.getCauseOfBlockage().toString(), containsString("‘reader’ lacks permission to run on ‘TestAgent’")); + } + + public static class AuthorizationCheckBuilder extends Builder { + + // "transient" is required for exclusion from serialization - see https://jenkins.io/redirect/class-filter/ + public transient String userName = null; + + @Override + public boolean prebuild(AbstractBuild build, BuildListener listener) { + userName = null; + return true; + } + + @Override + public boolean perform(AbstractBuild build, Launcher launcher, + BuildListener listener) throws InterruptedException, IOException { + userName = Jenkins.getAuthentication2().getName(); + return true; + } + + public static class DescriptorImpl extends BuildStepDescriptor { + @SuppressWarnings("rawtypes") + @Override + public boolean isApplicable(Class jobType) { + return true; + } + + @Override + public String getDisplayName() { + return "AuthorizationCheckBuilder"; + } + } + } +} diff --git a/src/test/java/org/jenkinsci/plugins/rolestrategy/RoleStrategyTest.java b/src/test/java/org/jenkinsci/plugins/rolestrategy/ConfigurationAsCodeTest.java similarity index 99% rename from src/test/java/org/jenkinsci/plugins/rolestrategy/RoleStrategyTest.java rename to src/test/java/org/jenkinsci/plugins/rolestrategy/ConfigurationAsCodeTest.java index adf00e56..771dc578 100644 --- a/src/test/java/org/jenkinsci/plugins/rolestrategy/RoleStrategyTest.java +++ b/src/test/java/org/jenkinsci/plugins/rolestrategy/ConfigurationAsCodeTest.java @@ -40,7 +40,7 @@ * @author Oleg Nenashev * @since 2.11 */ -public class RoleStrategyTest { +public class ConfigurationAsCodeTest { @Rule public JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule(); diff --git a/src/test/java/org/jenkinsci/plugins/rolestrategy/OwnershipTest.java b/src/test/java/org/jenkinsci/plugins/rolestrategy/OwnershipTest.java new file mode 100644 index 00000000..2da4d1ab --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/rolestrategy/OwnershipTest.java @@ -0,0 +1,91 @@ +package org.jenkinsci.plugins.rolestrategy; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.oneOf; + +import java.net.URL; +import java.util.Collections; + +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule.WebClient; + +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.util.NameValuePair; +import com.synopsys.arc.jenkins.plugins.ownership.OwnershipDescription; +import com.synopsys.arc.jenkins.plugins.ownership.nodes.OwnerNodeProperty; + +import hudson.model.Node; +import io.jenkins.plugins.casc.misc.ConfiguredWithCode; +import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule; + +public class OwnershipTest +{ + @Rule + public JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule(); + + @Test + @ConfiguredWithCode("OwnershipTest.yml") + public void currentUserIsPrimaryOwnerGrantsPermissions() throws Exception { + Node n = j.createOnlineSlave(); + n.getNodeProperties().add(new OwnerNodeProperty(n, new OwnershipDescription(true, "nodePrimaryTester", null))); + String nodeUrl = n.toComputer().getUrl(); + + WebClient wc = j.createWebClient(); + wc.withThrowExceptionOnFailingStatusCode(false); + wc.login("nodePrimaryTester", "nodePrimaryTester"); + HtmlPage managePage = wc.withThrowExceptionOnFailingStatusCode(false).goTo(String.format("%sconfigure", nodeUrl)); + assertThat(managePage.getWebResponse().getStatusCode(), is(200)); + URL testUrl = wc.createCrumbedUrl(String.format("%sdisconnect", nodeUrl)); + WebRequest request = new WebRequest(testUrl, HttpMethod.POST); + NameValuePair param = new NameValuePair("offlineMessage", "Disconnect for Test"); + request.setRequestParameters(Collections.singletonList(param)); + WebResponse response = wc.loadWebResponse(request); + assertThat(response.getStatusCode(), is(200)); + + testUrl = wc.createCrumbedUrl(String.format("%slaunchSlaveAgent", nodeUrl)); + request = new WebRequest(testUrl, HttpMethod.POST); + response = wc.loadWebResponse(request); + assertThat(response.getStatusCode(), is(oneOf(200, 302))); + + testUrl = wc.createCrumbedUrl(String.format("%sdoDelete", nodeUrl)); + request = new WebRequest(testUrl, HttpMethod.POST); + response = wc.loadWebResponse(request); + assertThat(response.getStatusCode(), is(200)); + } + + @Test + @ConfiguredWithCode("OwnershipTest.yml") + public void currentUserIsSecondaryOwnerGrantsPermissions() throws Exception { + Node n = j.createOnlineSlave(); + n.getNodeProperties().add(new OwnerNodeProperty(n, new OwnershipDescription(true, "nodePrimaryTester", Collections.singleton("nodeSecondaryTester")))); + String nodeUrl = n.toComputer().getUrl(); + + WebClient wc = j.createWebClient(); + wc.withThrowExceptionOnFailingStatusCode(false); + wc.login("nodeSecondaryTester", "nodeSecondaryTester"); + HtmlPage managePage = wc.withThrowExceptionOnFailingStatusCode(false).goTo(String.format("%sconfigure", nodeUrl)); + assertThat(managePage.getWebResponse().getStatusCode(), is(200)); + URL testUrl = wc.createCrumbedUrl(String.format("%sdisconnect", nodeUrl)); + WebRequest request = new WebRequest(testUrl, HttpMethod.POST); + NameValuePair param = new NameValuePair("offlineMessage", "Disconnect for Test"); + request.setRequestParameters(Collections.singletonList(param)); + WebResponse response = wc.loadWebResponse(request); + assertThat(response.getStatusCode(), is(200)); + + testUrl = wc.createCrumbedUrl(String.format("%slaunchSlaveAgent", nodeUrl)); + request = new WebRequest(testUrl, HttpMethod.POST); + response = wc.loadWebResponse(request); + assertThat(response.getStatusCode(), is(oneOf(200, 302))); + + testUrl = wc.createCrumbedUrl(String.format("%sdoDelete", nodeUrl)); + request = new WebRequest(testUrl, HttpMethod.POST); + response = wc.loadWebResponse(request); + assertThat(response.getStatusCode(), is(200)); + } + +} diff --git a/src/test/java/org/jenkinsci/plugins/rolestrategy/RoleAssignmentTest.java b/src/test/java/org/jenkinsci/plugins/rolestrategy/RoleAssignmentTest.java index 24072c74..769ceb10 100644 --- a/src/test/java/org/jenkinsci/plugins/rolestrategy/RoleAssignmentTest.java +++ b/src/test/java/org/jenkinsci/plugins/rolestrategy/RoleAssignmentTest.java @@ -1,11 +1,10 @@ package org.jenkinsci.plugins.rolestrategy; import hudson.model.User; +import hudson.security.ACL; +import hudson.security.ACLContext; import hudson.security.Permission; -import org.acegisecurity.Authentication; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextHolder; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -25,14 +24,9 @@ public void initSecurityRealm() { @LocalData @Test public void testRoleAssignment() { - SecurityContext seccon = SecurityContextHolder.getContext(); - Authentication orig = seccon.getAuthentication(); - seccon.setAuthentication(User.getById("alice", true).impersonate()); - try { + try (ACLContext c = ACL.as(User.getById("alice", true))) { assertTrue(j.jenkins.hasPermission(Permission.READ)); - } finally { - seccon.setAuthentication(orig); - } + } } } diff --git a/src/test/resources/org/jenkinsci/plugins/rolestrategy/AuthorizeProjectTest/config.xml b/src/test/resources/org/jenkinsci/plugins/rolestrategy/AuthorizeProjectTest/config.xml new file mode 100644 index 00000000..f070fca4 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/rolestrategy/AuthorizeProjectTest/config.xml @@ -0,0 +1,42 @@ + + + + jenkins.security.QueueItemAuthenticatorMonitor + + 2.303.3 + 2 + NORMAL + true + + + + + hudson.model.Hudson.Administer + + + admin + + + + + hudson.model.Hudson.Read + hudson.model.Item.Read + hudson.model.Item.Build + + + authenticated + + + + + + + hudson.model.Computer.Build + + + tester + + + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/rolestrategy/OwnershipTest.yml b/src/test/resources/org/jenkinsci/plugins/rolestrategy/OwnershipTest.yml new file mode 100644 index 00000000..4d3e3c49 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/rolestrategy/OwnershipTest.yml @@ -0,0 +1,95 @@ +jenkins: + authorizationStrategy: + roleBased: + roles: + agents: + - assignments: + - "authenticated" + name: "@CurrentUserIsPrimaryOwner" + pattern: ".*" + description: "User is Primary Owner" + permissions: + - "Manage ownership/Nodes" + - "Agent/Provision" + - "Agent/Configure" + - "Agent/ExtendedRead" + - "Agent/Delete" + - "Agent/Connect" + - "Agent/Build" + - "Agent/Disconnect" + - assignments: + - "authenticated" + name: "@CurrentUserIsOwner" + pattern: ".*" + description: "User is Owner" + permissions: + - "Manage ownership/Nodes" + - "Agent/Provision" + - "Agent/Configure" + - "Agent/ExtendedRead" + - "Agent/Delete" + - "Agent/Connect" + - "Agent/Build" + - "Agent/Disconnect" + global: + - assignments: + - "admin" + name: "admin" + pattern: ".*" + description: "Admin Users" + permissions: + - "Overall/Administer" + - assignments: + - "authenticated" + name: "reader" + pattern: ".*" + permissions: + - "Overall/Read" + items: + - assignments: + - "authenticated" + name: "@CurrentUserIsOwner" + pattern: ".*" + description: "User is Owner" + permissions: + - "Job/Move" + - "Job/Build" + - "Job/Create" + - "Job/Discover" + - "Job/Read" + - "Job/Cancel" + - "Job/ExtendedRead" + - "Job/Delete" + - "Job/Configure" + - "Job/Workspace" + - "Job/ViewStatus" # System for test + - assignments: + - "authenticated" + name: "@CurrentUserIsPrimaryOwner" + pattern: ".*" + description: "User is Primary Owner" + permissions: + - "Job/Move" + - "Job/Build" + - "Job/Create" + - "Job/Discover" + - "Manage ownership/Jobs" + - "Job/Read" + - "Job/Cancel" + - "Job/ExtendedRead" + - "Job/Delete" + - "Job/Configure" + - "Job/Workspace" + - "Job/ViewStatus" # System for test + + securityRealm: + local: + allowsSignup: false + users: + - id: "admin" + password: "1234" + - id: "nodePrimaryTester" + password: "nodePrimaryTester" + - id: "nodeSecondaryTester" + password: "nodeSecondaryTester" +