Skip to content

Commit

Permalink
Merge pull request #209 from AndreBrinkop/add-jenkins-agent-support
Browse files Browse the repository at this point in the history
Add Jenkins agent support for GitHub Committer Authorization Strategy
  • Loading branch information
scurvydoggo committed Jul 31, 2023
2 parents 7664e3b + fc00869 commit c7477b1
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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() &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -64,6 +61,7 @@ public class GithubRequireOrganizationMembershipACL extends ACL {

private final List<String> organizationNameList;
private final List<String> adminUserNameList;
private String agentUserName;
private final boolean authenticatedUserReadPermission;
private final boolean useRepositoryPermissions;
private final boolean authenticatedUserCreateJobPermission;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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,
Expand All @@ -328,6 +346,8 @@ public GithubRequireOrganizationMembershipACL cloneForProject(AbstractItem item)
this.allowAnonymousReadPermission,
this.allowAnonymousJobStatusPermission,
item);
acl.setAgentUserName(agentUserName);
return acl;
}

public GithubRequireOrganizationMembershipACL(List<String> adminUserNameList,
Expand Down Expand Up @@ -362,6 +382,11 @@ public List<String> getAdminUserNameList() {
return adminUserNameList;
}

public void setAgentUserName(String agentUserName) {
this.agentUserName = agentUserName;
}
public String getAgentUserName() { return agentUserName; }

public boolean isUseRepositoryPermissions() {
return useRepositoryPermissions;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
<f:textbox />
</f:entry>

<f:entry title="Agent User Name" field="agentUserName" help="/plugin/github-oauth/help/auth/agent-user-name-help.html" >
<f:textbox />
</f:entry>

<f:entry title="Participant in Organization" field="organizationNames" help="/plugin/github-oauth/help/auth/organization-names-help.html">
<f:textbox />
</f:entry>
Expand Down
3 changes: 3 additions & 0 deletions src/main/webapp/help/auth/agent-user-name-help.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
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.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -130,7 +131,7 @@ private void mockJenkins(MockedStatic<Jenkins> mockedJenkins) {
new GrantedAuthority[]{new GrantedAuthorityImpl("anonymous")});

private GithubRequireOrganizationMembershipACL createACL() {
return new GithubRequireOrganizationMembershipACL(
GithubRequireOrganizationMembershipACL acl = new GithubRequireOrganizationMembershipACL(
"admin",
"myOrg",
authenticatedUserReadPermission,
Expand All @@ -140,6 +141,8 @@ private GithubRequireOrganizationMembershipACL createACL() {
allowAnonymousCCTrayPermission,
allowAnonymousReadPermission,
allowAnonymousJobStatusPermission);
acl.setAgentUserName("agent");
return acl;
}

private GithubRequireOrganizationMembershipACL aclForProject(Project project) {
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit c7477b1

Please sign in to comment.