diff --git a/src/main/java/org/jenkinsci/plugins/GithubAuthorizationStrategy.java b/src/main/java/org/jenkinsci/plugins/GithubAuthorizationStrategy.java index b9927aa6..e3767625 100644 --- a/src/main/java/org/jenkinsci/plugins/GithubAuthorizationStrategy.java +++ b/src/main/java/org/jenkinsci/plugins/GithubAuthorizationStrategy.java @@ -30,6 +30,7 @@ of this software and associated documentation files (the "Software"), to deal import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.multibranch.BranchJobProperty; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; import java.util.Collection; import java.util.Collections; @@ -140,6 +141,23 @@ public String getAdminUserNames() { return StringUtils.join(rootACL.getAdminUserNameList().iterator(), ", "); } + /** Set the agent username. We use a setter instead of a constructor to make this an optional field + * to avoid a breaking change. + * @see org.jenkinsci.plugins.GithubRequireOrganizationMembershipACL#setAgentUserName(String) + */ + @DataBoundSetter + public void setAgentUserName(String agentUserName) { + rootACL.setAgentUserName(agentUserName); + } + + /** + * @return agentUserName + * @see GithubRequireOrganizationMembershipACL#getAgentUserName() + */ + public String getAgentUserName() { + return rootACL.getAgentUserName(); + } + /** * @return isUseRepositoryPermissions * @see org.jenkinsci.plugins.GithubRequireOrganizationMembershipACL#isUseRepositoryPermissions() @@ -208,6 +226,7 @@ public boolean equals(Object object){ GithubAuthorizationStrategy obj = (GithubAuthorizationStrategy) object; return this.getOrganizationNames().equals(obj.getOrganizationNames()) && this.getAdminUserNames().equals(obj.getAdminUserNames()) && + this.getAgentUserName().equals(obj.getAgentUserName()) && this.isUseRepositoryPermissions() == obj.isUseRepositoryPermissions() && this.isAuthenticatedUserCreateJobPermission() == obj.isAuthenticatedUserCreateJobPermission() && this.isAuthenticatedUserReadPermission() == obj.isAuthenticatedUserReadPermission() && diff --git a/src/main/java/org/jenkinsci/plugins/GithubRequireOrganizationMembershipACL.java b/src/main/java/org/jenkinsci/plugins/GithubRequireOrganizationMembershipACL.java index a823c017..a7c8d81d 100644 --- a/src/main/java/org/jenkinsci/plugins/GithubRequireOrganizationMembershipACL.java +++ b/src/main/java/org/jenkinsci/plugins/GithubRequireOrganizationMembershipACL.java @@ -26,6 +26,7 @@ of this software and associated documentation files (the "Software"), to deal */ package org.jenkinsci.plugins; +import hudson.model.*; import org.acegisecurity.Authentication; import org.jenkinsci.plugins.github_branch_source.GitHubSCMSource; import org.jenkinsci.plugins.workflow.job.WorkflowJob; @@ -41,10 +42,6 @@ of this software and associated documentation files (the "Software"), to deal import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import hudson.model.AbstractItem; -import hudson.model.AbstractProject; -import hudson.model.Describable; -import hudson.model.Item; import hudson.plugins.git.GitSCM; import hudson.plugins.git.UserRemoteConfig; import hudson.security.ACL; @@ -64,6 +61,7 @@ public class GithubRequireOrganizationMembershipACL extends ACL { private final List organizationNameList; private final List adminUserNameList; + private String agentUserName; private final boolean authenticatedUserReadPermission; private final boolean useRepositoryPermissions; private final boolean authenticatedUserCreateJobPermission; @@ -102,6 +100,12 @@ public boolean hasPermission(@NonNull Authentication a, @NonNull Permission perm return true; } + // Grant agent permissions to agent user + if (candidateName.equalsIgnoreCase(agentUserName) && checkAgentUserPermission(permission)) { + log.finest("Granting Agent Connect rights to user " + candidateName); + return true; + } + // Are they trying to read? if (checkReadPermission(permission)) { // if we support authenticated read return early @@ -153,6 +157,12 @@ else if (testBuildPermission(permission) && isInWhitelistedOrgs(authenticationTo return true; } + // Grant agent permissions to agent user + if (authenticatedUserName.equalsIgnoreCase(agentUserName) && checkAgentUserPermission(permission)) { + log.finest("Granting Agent Connect rights to user " + authenticatedUserName); + return true; + } + if (authenticatedUserName.equals("anonymous")) { if (checkJobStatusPermission(permission) && allowAnonymousJobStatusPermission) { return true; @@ -239,6 +249,13 @@ private boolean checkReadPermission(@NonNull Permission permission) { || id.equals("hudson.model.Item.Read")); } + private boolean checkAgentUserPermission(@NonNull Permission permission) { + return permission.equals(Hudson.READ) + || permission.equals(Computer.CREATE) + || permission.equals(Computer.CONNECT) + || permission.equals(Computer.CONFIGURE); + } + private boolean checkJobStatusPermission(@NonNull Permission permission) { return permission.getId().equals("hudson.model.Item.ViewStatus"); } @@ -314,10 +331,11 @@ public GithubRequireOrganizationMembershipACL(String adminUserNames, } this.item = null; + this.agentUserName = ""; // Initially blank - populated by a setter since this field is optional } public GithubRequireOrganizationMembershipACL cloneForProject(AbstractItem item) { - return new GithubRequireOrganizationMembershipACL( + GithubRequireOrganizationMembershipACL acl = new GithubRequireOrganizationMembershipACL( this.adminUserNameList, this.organizationNameList, this.authenticatedUserReadPermission, @@ -328,6 +346,8 @@ public GithubRequireOrganizationMembershipACL cloneForProject(AbstractItem item) this.allowAnonymousReadPermission, this.allowAnonymousJobStatusPermission, item); + acl.setAgentUserName(agentUserName); + return acl; } public GithubRequireOrganizationMembershipACL(List adminUserNameList, @@ -362,6 +382,11 @@ public List getAdminUserNameList() { return adminUserNameList; } + public void setAgentUserName(String agentUserName) { + this.agentUserName = agentUserName; + } + public String getAgentUserName() { return agentUserName; } + public boolean isUseRepositoryPermissions() { return useRepositoryPermissions; } diff --git a/src/main/resources/org/jenkinsci/plugins/GithubAuthorizationStrategy/config.jelly b/src/main/resources/org/jenkinsci/plugins/GithubAuthorizationStrategy/config.jelly index 2455a404..abd722d1 100644 --- a/src/main/resources/org/jenkinsci/plugins/GithubAuthorizationStrategy/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/GithubAuthorizationStrategy/config.jelly @@ -8,6 +8,10 @@ + + + + diff --git a/src/main/webapp/help/auth/agent-user-name-help.html b/src/main/webapp/help/auth/agent-user-name-help.html new file mode 100644 index 00000000..eee865b0 --- /dev/null +++ b/src/main/webapp/help/auth/agent-user-name-help.html @@ -0,0 +1,3 @@ +
+If you are using inbound Jenkins agents, this is the user that is used for authenticating agents. This user will receive rights to create, connect and configure agents. +
\ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/GithubRequireOrganizationMembershipACLTest.java b/src/test/java/org/jenkinsci/plugins/GithubRequireOrganizationMembershipACLTest.java index c0e99bec..6a7a9b99 100644 --- a/src/test/java/org/jenkinsci/plugins/GithubRequireOrganizationMembershipACLTest.java +++ b/src/test/java/org/jenkinsci/plugins/GithubRequireOrganizationMembershipACLTest.java @@ -60,6 +60,7 @@ of this software and associated documentation files (the "Software"), to deal import java.util.Collections; import java.util.List; +import hudson.model.Computer; import hudson.model.Hudson; import hudson.model.Item; import hudson.model.Messages; @@ -130,7 +131,7 @@ private void mockJenkins(MockedStatic mockedJenkins) { new GrantedAuthority[]{new GrantedAuthorityImpl("anonymous")}); private GithubRequireOrganizationMembershipACL createACL() { - return new GithubRequireOrganizationMembershipACL( + GithubRequireOrganizationMembershipACL acl = new GithubRequireOrganizationMembershipACL( "admin", "myOrg", authenticatedUserReadPermission, @@ -140,6 +141,8 @@ private GithubRequireOrganizationMembershipACL createACL() { allowAnonymousCCTrayPermission, allowAnonymousReadPermission, allowAnonymousJobStatusPermission); + acl.setAgentUserName("agent"); + return acl; } private GithubRequireOrganizationMembershipACL aclForProject(Project project) { @@ -554,6 +557,30 @@ public void testCannotReadRepositoryWithInvalidRepoUrl() throws IOException { } } + @Test + public void testAgentUserCanCreateConnectAndConfigureAgents() { + GithubAuthenticationToken authenticationToken = Mockito.mock(GithubAuthenticationToken.class); + Mockito.when(authenticationToken.isAuthenticated()).thenReturn(true); + Mockito.when(authenticationToken.getName()).thenReturn("agent"); + GithubRequireOrganizationMembershipACL acl = createACL(); + + assertTrue(acl.hasPermission(authenticationToken, Computer.CREATE)); + assertTrue(acl.hasPermission(authenticationToken, Computer.CONFIGURE)); + assertTrue(acl.hasPermission(authenticationToken, Computer.CONNECT)); + } + + @Test + public void testAuthenticatedCanNotCreateConnectAndConfigureAgents() { + GithubAuthenticationToken authenticationToken = Mockito.mock(GithubAuthenticationToken.class); + Mockito.when(authenticationToken.isAuthenticated()).thenReturn(true); + Mockito.when(authenticationToken.getName()).thenReturn("authenticated"); + GithubRequireOrganizationMembershipACL acl = createACL(); + + assertFalse(acl.hasPermission(authenticationToken, Computer.CREATE)); + assertFalse(acl.hasPermission(authenticationToken, Computer.CONFIGURE)); + assertFalse(acl.hasPermission(authenticationToken, Computer.CONNECT)); + } + @Test public void testAnonymousCanViewJobStatusWhenGranted() { this.allowAnonymousJobStatusPermission = true;