Skip to content

Commit

Permalink
[JENKINS-51672][JENKINS-41731] get users role in pipeline (#324)
Browse files Browse the repository at this point in the history
added the pipeline steps
currentUserGlobalRoles
currentUserItemRoles
  • Loading branch information
mawinter69 authored Aug 4, 2023
1 parent f4495e3 commit a5f2767
Show file tree
Hide file tree
Showing 13 changed files with 618 additions and 4 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ You can assign roles to users and user groups using the _Assign Roles_ screen

![Assign roles](/docs/images/assignRoles.png)

### Getting roles in pipelines
There are 2 steps available in pipeline jobs that allow to get the roles of the user running the build.
When the build was triggered by a user via the UI or the REST API, the roles of this user are returned. In case the build was triggered
by the times or an SCM event there is no dedicated user available and the `SYSTEM` user is used. This user is considered like an admin and will have all roles.<br/>
With the [Authorize Project](https://plugins.jenkins.io/authorize-project/) plugin, it is possible to make builds triggered by timer or an SCM event
to run as a specific user which is then used or run as `anonymous`. For `anonymous` it means no roles are returned. The user that triggered the build will always take
precedence over the user that is configured via `Authorize Project`.

#### currentUserGlobalRoles
The step `currentUserGlobalRoles` will return all global roles of the user.

#### currentUserItemRoles
The step `currentUserGlobalRoles` will return the item roles of the user. By default, it returns only those roles that
match the currently building pipeline. The parameter `showAllRoles` will return all item roles of the user.

### Rest API

The Rest API allows to query the current roles and assignments and to do changes to them.
Expand Down
14 changes: 14 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@
<artifactId>cloudbees-folder</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.jenkins.configuration-as-code</groupId>
<artifactId>test-harness</artifactId>
Expand All @@ -106,6 +111,15 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>authorize-project</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-basic-steps</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

/**
* Class holding a map for each kind of {@link AccessControlled} object, associating each {@link Role} with the
Expand Down Expand Up @@ -402,8 +404,8 @@ public void deleteSids(String sid) {
/**
* Clear specific role associated to the given sid.
*
* @param sid The sid for wich you want to clear the {@link Role}s
* @param rolename The role for wich you want to clear the {@link Role}s
* @param sid The sid for which you want to clear the {@link Role}s
* @param rolename The role for which you want to clear the {@link Role}s
* @since 2.6.0
*/
public void deleteRoleSid(PermissionEntry sid, String rolename) {
Expand All @@ -421,8 +423,8 @@ public void deleteRoleSid(PermissionEntry sid, String rolename) {
* Clear specific role associated to the given sid.
* This will only find sids of type {@link AuthorizationType#EITHER}
*
* @param sid The sid for wich you want to clear the {@link Role}s
* @param rolename The role for wich you want to clear the {@link Role}s
* @param sid The sid for which you want to clear the {@link Role}s
* @param rolename The role for which you want to clear the {@link Role}s
* @since 2.6.0
* @deprecated use {@link #deleteRoleSid(PermissionEntry, String)}
*/
Expand Down Expand Up @@ -597,6 +599,51 @@ public Set<String> getSidsForRole(String roleName) {
return null;
}

/**
* Get all roles associated with the given User.
*
* @param user The User for which to get the roles
* @return a set of roles
* @throws UsernameNotFoundException when user is not found
*/
@NonNull
public Set<String> getRolesForUser(User user) throws UsernameNotFoundException {
return getRolesForAuth(user.impersonate2());
}

/**
* Get all roles associated with the given Authentication.
*
* @param auth The Authentication for which to get the roles
* @return a set of roles
*/
@NonNull
@Restricted(NoExternalUse.class)
public Set<String> getRolesForAuth(Authentication auth) {
PermissionEntry userEntry = new PermissionEntry(AuthorizationType.USER, auth.getPrincipal().toString());
Set<String> roleSet = new HashSet<>(getRolesForSidEntry(userEntry));
for (GrantedAuthority group : auth.getAuthorities()) {
PermissionEntry groupEntry = new PermissionEntry(AuthorizationType.GROUP, group.getAuthority());
roleSet.addAll(getRolesForSidEntry(groupEntry));
}
return roleSet;
}

private Set<String> getRolesForSidEntry(PermissionEntry entry) {

Set<String> roleSet = new HashSet<>();
new RoleWalker() {
@Override
public void perform(Role current) {
if (grantedRoles.get(current).contains(entry)) {
roleSet.add(current.getName());
}
}
};

return roleSet;
}

/**
* Create a sub-map of this {@link RoleMap} containing {@link Role}s that are applicable on the given
* {@code itemNamePrefix}.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.jenkinsci.plugins.rolestrategy.pipeline;

import com.michelin.cio.hudson.plugins.rolestrategy.Role;
import com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy;
import com.michelin.cio.hudson.plugins.rolestrategy.RoleMap;
import com.synopsys.arc.jenkins.plugins.rolestrategy.RoleType;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.Cause;
import hudson.model.Run;
import hudson.model.User;
import hudson.security.ACL;
import hudson.security.AuthorizationStrategy;
import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution;
import org.springframework.security.core.Authentication;

/**
* Base class for the pipeline steps.
*/
public abstract class AbstractUserRolesStep extends Step {

/**
* Step Execution.
*/
protected static class Execution extends SynchronousNonBlockingStepExecution<Set<String>> {
protected final RoleType roleType;

public Execution(@NonNull StepContext context, RoleType roleType) {
super(context);
this.roleType = roleType;
}

protected RoleMap getRoleMap() throws IOException, InterruptedException {
AuthorizationStrategy strategy = Jenkins.get().getAuthorizationStrategy();
if (strategy instanceof RoleBasedAuthorizationStrategy) {
RoleBasedAuthorizationStrategy rbas = (RoleBasedAuthorizationStrategy) strategy;
return rbas.getRoleMap(roleType);
}
return null;
}

@Override
protected Set<String> run() throws Exception {
Set<String> roleSet = new HashSet<>();
Authentication auth = getAuthentication();
if (auth == null) {
return roleSet;
}
RoleMap roleMap = getRoleMap();
if (roleMap != null) {
if (auth == ACL.SYSTEM2) {
return roleMap.getRoles().stream().map(Role::getName).collect(Collectors.toSet());
}
return roleMap.getRolesForAuth(auth);
}
return roleSet;
}


private Authentication getAuthentication() throws IOException, InterruptedException {
final Run<?, ?> run = Objects.requireNonNull(getContext().get(Run.class));
Cause.UserIdCause cause = run.getCause(Cause.UserIdCause.class);
if (cause != null) {
User causeUser = User.getById(cause.getUserId(), false);
if (causeUser != null) {
return causeUser.impersonate2();
}
}
Authentication auth = Jenkins.getAuthentication2();
if (ACL.isAnonymous2(auth)) {
return null;
}
return auth;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.jenkinsci.plugins.rolestrategy.pipeline;

import com.synopsys.arc.jenkins.plugins.rolestrategy.RoleType;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Run;
import java.util.Collections;
import java.util.Set;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.kohsuke.stapler.DataBoundConstructor;

/**
* Pipeline step that returns the users global roles.
*/
public class UserGlobalRoles extends AbstractUserRolesStep {

@DataBoundConstructor
public UserGlobalRoles() {
}

@Override
public StepExecution start(StepContext context) throws Exception {
return new Execution(context, RoleType.Global);
}

/**
* The descriptor of the step.
*/
@Extension
public static final class DescriptorImpl extends StepDescriptor {

@Override
public Set<? extends Class<?>> getRequiredContext() {
return Collections.singleton(Run.class);
}

@NonNull
@Override public String getDisplayName() {
return "Current Users Global Roles";
}

@Override
public String getFunctionName() {
return "currentUserGlobalRoles";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.jenkinsci.plugins.rolestrategy.pipeline;

import com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy;
import com.michelin.cio.hudson.plugins.rolestrategy.RoleMap;
import com.synopsys.arc.jenkins.plugins.rolestrategy.RoleType;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Job;
import hudson.model.Run;
import hudson.security.AuthorizationStrategy;
import java.io.IOException;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

/**
* Pipeline step that returns the users item roles.
*/
public class UserItemRoles extends AbstractUserRolesStep {

private boolean showAllRoles;

@DataBoundConstructor
public UserItemRoles() {
}

public boolean isShowAllRoles() {
return showAllRoles;
}

@DataBoundSetter
public void setShowAllRoles(boolean showAllRoles) {
this.showAllRoles = showAllRoles;
}

@Override
public StepExecution start(StepContext context) throws Exception {
return new ItemRolesExecution(context, RoleType.Project, showAllRoles);
}

/**
* Step Execution.
*/
public static class ItemRolesExecution extends Execution {

private final boolean showAllRoles;

public ItemRolesExecution(@NonNull StepContext context, RoleType roleType, boolean showAllRoles) {
super(context, roleType);
this.showAllRoles = showAllRoles;
}

@Override
protected RoleMap getRoleMap() throws IOException, InterruptedException {
AuthorizationStrategy strategy = Jenkins.get().getAuthorizationStrategy();
if (strategy instanceof RoleBasedAuthorizationStrategy) {
RoleBasedAuthorizationStrategy rbas = (RoleBasedAuthorizationStrategy) strategy;
RoleMap roleMap = rbas.getRoleMap(roleType);
if (showAllRoles) {
return roleMap;
} else {
final Run<?, ?> run = Objects.requireNonNull(getContext().get(Run.class));
Job<?, ?> job = run.getParent();
return roleMap.newMatchingRoleMap(job.getFullName());
}
}
return null;
}
}

/**
* The descriptor.
*/
@Extension
public static final class DescriptorImpl extends StepDescriptor {

@Override
public Set<? extends Class<?>> getRequiredContext() {
return Collections.singleton(Run.class);
}

@NonNull
@Override public String getDisplayName() {
return "Current Users Item Roles";
}

@Override
public String getFunctionName() {
return "currentUserItemRoles";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div>
Returns a list of all global roles of the user that started the build. This includes roles assigned via groups.
When the run is triggered by an SCM event or by the timer, the build usually runs as the <em>System</em> user. This user is
considered as having all roles.<br/>
You can use the <a href="https://plugins.jenkins.io/authorize-project/" target="_blank">Authorize Project</a> plugin
to run the builds as a different user. When running as <em>anonymous</em>, an empty list is returned.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:st="jelly:stapler">
<f:entry title="Show all roles" field="showAllRoles">
<f:checkbox/>
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
If checked, all item roles of the user are returned. Otherwise only roles matching the pipeline job are returned.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div>
Returns a list of all item roles of the user that started the build. This includes roles assigned via groups.
When the run is triggered by an SCM event or by the timer, the build usually runs as the <em>System</em> user. This user is
considered as having all roles.<br/>
You can use the <a href="https://plugins.jenkins.io/authorize-project/" target="_blank">Authorize Project</a> plugin
to run the builds as a different user. When running as <em>anonymous</em>, an empty list is returned.
</div>
Loading

0 comments on commit a5f2767

Please sign in to comment.