Skip to content

Commit

Permalink
[JENKINS-67757] Permission templates (#237)
Browse files Browse the repository at this point in the history
Permission templates allow to define a set of permissions only once and then reuse them for many roles.
Instead of having to repeatedly enter all the permissions you now just select the template and get all permissions.
The permissions of roles that are based on a templated can't be modified.
Changing a template will affect all roles that use the template.

See JENKINS-69318 and JENKINS-67757
  • Loading branch information
mawinter69 authored Jul 14, 2023
1 parent 4d4f871 commit ae39b13
Show file tree
Hide file tree
Showing 31 changed files with 1,338 additions and 173 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,17 @@ You can define roles by using the _Manages Roles_ screen. It is possible to defi
Only jobs matching the pattern can be created. When granting `Job/Create` you should also grant `Job/Configure` and `Job/Read` otherwise you will
be able to create new jobs but you will not be able to configure them. Global Permissions are not required.


![Managing roles](/docs/images/manageRoles.png)

#### Permission Templates
Permission Templates simplify the administration of roles when you need to maintain many roles with identical permissions but different patterns.
Templates are only available for _Item Roles_. The permissions of roles based on a template can't be modified directly. Modifying the template will
immediately modify the linked roles after saving the changes.

Deleting a template that is still in use requires confirmation. In case you still delete it, the roles stay with the given permissions but the
correlation to the template is removed.

### Assigning roles

You can assign roles to users and user groups using the _Assign Roles_ screen
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.michelin.cio.hudson.plugins.rolestrategy;

import com.synopsys.arc.jenkins.plugins.rolestrategy.RoleType;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.security.AuthorizationStrategy;
import hudson.security.Permission;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import jenkins.model.ProjectNamingStrategy;
import org.jenkinsci.plugins.rolestrategy.RoleBasedProjectNamingStrategy;
import org.jenkinsci.plugins.rolestrategy.permissions.PermissionHelper;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;

/**
* Holds a set of permissions for the role generator.
*/
@Restricted(NoExternalUse.class)
public class PermissionTemplate implements Comparable<PermissionTemplate> {

private static final Logger LOGGER = Logger.getLogger(PermissionTemplate.class.getName());

private final String name;
private final Set<Permission> permissions = new HashSet<>();

/**
* Create a new PermissionTemplate.
*
* @param name the name of the template
* @param permissions the set of permissions of this template
*/
@DataBoundConstructor
public PermissionTemplate(String name, Set<String> permissions) {
this(PermissionHelper.fromStrings(permissions, true), name);
}

/**
* Create a new PermissionTemplate.
*
* @param name the name of the template
* @param permissions the set of permissions of this template
*/
public PermissionTemplate(Set<Permission> permissions, String name) {
this.name = name;
for (Permission perm : permissions) {
if (perm == null) {
LOGGER.log(Level.WARNING, "Found some null permission(s) in role " + this.name, new IllegalArgumentException());
} else {
this.permissions.add(perm);
}
}
}

/**
* Checks whether the template is used by one or more roles.#
*
* @return true when template is used.
*/
public boolean isUsed() {
AuthorizationStrategy auth = Jenkins.get().getAuthorizationStrategy();
ProjectNamingStrategy pns = Jenkins.get().getProjectNamingStrategy();
if (auth instanceof RoleBasedAuthorizationStrategy && pns instanceof RoleBasedProjectNamingStrategy) {
RoleBasedAuthorizationStrategy rbas = (RoleBasedAuthorizationStrategy) auth;
Map<Role, Set<PermissionEntry>> roleMap = rbas.getGrantedRolesEntries(RoleType.Project);
for (Role role : roleMap.keySet()) {
if (Objects.equals(name, role.getTemplateName())) {
return true;
}
}
}
return false;
}

public String getName() {
return name;
}

public Set<Permission> getPermissions() {
return Collections.unmodifiableSet(permissions);
}

/**
* Checks if the role holds the given {@link Permission}.
*
* @param permission The permission you want to check
* @return True if the role holds this permission
*/
public Boolean hasPermission(Permission permission) {
return permissions.contains(permission);
}

@Override
public int hashCode() {
return name.hashCode();
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PermissionTemplate other = (PermissionTemplate) obj;
return Objects.equals(name, other.name);
}

@Override
public int compareTo(@NonNull PermissionTemplate o) {
return name.compareTo(o.name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Util;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
Expand Down Expand Up @@ -64,6 +66,12 @@ public final class Role implements Comparable {
@CheckForNull
private final String description;

/**
* Flag to indicate that the role is template based.
*/
@CheckForNull
private String templateName;

/**
* {@link Permission}s hold by the role.
*/
Expand Down Expand Up @@ -94,10 +102,16 @@ public final class Role implements Comparable {

// TODO: comment is used for erasure cleanup only
@DataBoundConstructor
public Role(@NonNull String name, @CheckForNull String pattern, @CheckForNull Set<String> permissionIds,
@CheckForNull String description, String templateName) {
this(name, Pattern.compile(pattern != null ? pattern : GLOBAL_ROLE_PATTERN), PermissionHelper.fromStrings(permissionIds, true),
description, templateName);
}

public Role(@NonNull String name, @CheckForNull String pattern, @CheckForNull Set<String> permissionIds,
@CheckForNull String description) {
this(name, Pattern.compile(pattern != null ? pattern : GLOBAL_ROLE_PATTERN), PermissionHelper.fromStrings(permissionIds, true),
description);
description, "");
}

/**
Expand All @@ -106,11 +120,26 @@ public Role(@NonNull String name, @CheckForNull String pattern, @CheckForNull Se
* @param name The role name
* @param pattern The pattern matching {@link AccessControlled} objects names
* @param permissions The {@link Permission}s associated to the role. {@code null} permissions will be ignored.
* @param description A description for the role
*/
public Role(String name, Pattern pattern, Set<Permission> permissions, @CheckForNull String description) {
this(name, pattern, permissions, description, "");
}

/**
* Create a Role.
*
* @param name The role name
* @param pattern The pattern matching {@link AccessControlled} objects names
* @param permissions The {@link Permission}s associated to the role. {@code null} permissions will be ignored.
* @param description A description for the role
* @param templateName True to mark this role as generated
*/
public Role(String name, Pattern pattern, Set<Permission> permissions, @CheckForNull String description, String templateName) {
this.name = name;
this.pattern = pattern;
this.description = description;
this.templateName = templateName;
for (Permission perm : permissions) {
if (perm == null) {
LOGGER.log(Level.WARNING, "Found some null permission(s) in role " + this.name, new IllegalArgumentException());
Expand All @@ -120,6 +149,14 @@ public Role(String name, Pattern pattern, Set<Permission> permissions, @CheckFor
}
}

public void setTemplateName(@CheckForNull String templateName) {
this.templateName = templateName;
}

public String getTemplateName() {
return templateName;
}

/**
* Getter for the role name.
*
Expand All @@ -144,7 +181,39 @@ public Pattern getPattern() {
* @return {@link Permission}s set
*/
public Set<Permission> getPermissions() {
return permissions;
return Collections.unmodifiableSet(permissions);
}

/**
* Update the permissions of the role.
*
* Internal use only.
*/
private void setPermissions(Set<Permission> permissions) {
synchronized (this.permissions) {
this.permissions.clear();
this.permissions.addAll(permissions);
cachedHashCode = _hashCode();
}
}

/**
* Updates the permissions from the used template.
*/
public void refreshPermissionsFromTemplate(Set<PermissionTemplate> permissionTemplates) {
if (Util.fixEmptyAndTrim(templateName) != null) {
boolean found = false;
for (PermissionTemplate pt : permissionTemplates) {
if (pt.getName().equals(templateName)) {
setPermissions(pt.getPermissions());
found = true;
break;
}
}
if (! found) {
this.templateName = null;
}
}
}

/**
Expand All @@ -164,7 +233,9 @@ public String getDescription() {
* @return True if the role holds this permission
*/
public Boolean hasPermission(Permission permission) {
return permissions.contains(permission);
synchronized (this.permissions) {
return permissions.contains(permission);
}
}

/**
Expand All @@ -174,7 +245,9 @@ public Boolean hasPermission(Permission permission) {
* @return True if the role holds any of the given {@link Permission}s
*/
public Boolean hasAnyPermission(Set<Permission> permissions) {
return CollectionUtils.containsAny(this.permissions, permissions);
synchronized (this.permissions) {
return CollectionUtils.containsAny(this.permissions, permissions);
}
}

/**
Expand Down Expand Up @@ -207,7 +280,9 @@ private int _hashCode() {
int hash = 7;
hash = 53 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 53 * hash + (this.pattern != null ? this.pattern.hashCode() : 0);
hash = 53 * hash + (this.permissions != null ? this.permissions.hashCode() : 0);
synchronized (this.permissions) {
hash = 53 * hash + this.permissions.hashCode();
}
return hash;
}

Expand All @@ -229,8 +304,10 @@ public boolean equals(Object obj) {
if (!Objects.equals(this.pattern, other.pattern)) {
return false;
}
if (!Objects.equals(this.permissions, other.permissions)) {
return false;
synchronized (this.permissions) {
if (!Objects.equals(this.permissions, other.permissions)) {
return false;
}
}
return true;
}
Expand Down
Loading

0 comments on commit ae39b13

Please sign in to comment.