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 committed 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) {

Check warning on line 53 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 53 is only partially covered, one branch is missing
LOGGER.log(Level.WARNING, "Found some null permission(s) in role " + this.name, new IllegalArgumentException());

Check warning on line 54 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 54 is not covered by tests
} 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();

Check warning on line 67 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 67 is not covered by tests
ProjectNamingStrategy pns = Jenkins.get().getProjectNamingStrategy();

Check warning on line 68 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 68 is not covered by tests
if (auth instanceof RoleBasedAuthorizationStrategy && pns instanceof RoleBasedProjectNamingStrategy) {

Check warning on line 69 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 69 is only partially covered, 4 branches are missing
RoleBasedAuthorizationStrategy rbas = (RoleBasedAuthorizationStrategy) auth;

Check warning on line 70 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 70 is not covered by tests
Map<Role, Set<PermissionEntry>> roleMap = rbas.getGrantedRolesEntries(RoleType.Project);

Check warning on line 71 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 71 is not covered by tests
for (Role role : roleMap.keySet()) {

Check warning on line 72 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 72 is only partially covered, 2 branches are missing
if (Objects.equals(name, role.getTemplateName())) {

Check warning on line 73 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 73 is only partially covered, 2 branches are missing
return true;

Check warning on line 74 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 74 is not covered by tests
}
}

Check warning on line 76 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 76 is not covered by tests
}
return false;

Check warning on line 78 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 78 is not covered by tests
}

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);

Check warning on line 96 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 96 is not covered by tests
}

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

Check warning on line 101 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 101 is not covered by tests
}

@Override
public boolean equals(Object obj) {
if (this == obj) {

Check warning on line 106 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 106 is only partially covered, 2 branches are missing
return true;

Check warning on line 107 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 107 is not covered by tests
}
if (obj == null) {

Check warning on line 109 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 109 is only partially covered, 2 branches are missing
return false;

Check warning on line 110 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 110 is not covered by tests
}
if (getClass() != obj.getClass()) {

Check warning on line 112 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 112 is only partially covered, 2 branches are missing
return false;

Check warning on line 113 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 113 is not covered by tests
}
final PermissionTemplate other = (PermissionTemplate) obj;

Check warning on line 115 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 115 is not covered by tests
return Objects.equals(name, other.name);

Check warning on line 116 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/PermissionTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 116 is not covered by tests
}

@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

Check warning on line 103 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/Role.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: 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),

Check warning on line 107 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/Role.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 107 is only partially covered, 2 branches are missing
description, templateName);
}

Check warning on line 109 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/Role.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 109 is not covered by tests

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;

Check warning on line 153 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/Role.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 153 is not covered by tests
}

Check warning on line 154 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/Role.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 154 is not covered by tests

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) {

Check warning on line 206 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/Role.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 206 is only partially covered, one branch is missing
if (pt.getName().equals(templateName)) {
setPermissions(pt.getPermissions());
found = true;
break;
}
}
if (! found) {

Check warning on line 213 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/Role.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 213 is only partially covered, one branch is missing
this.templateName = null;

Check warning on line 214 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/Role.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 214 is not covered by tests
}
}
}

/**
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) {

Check warning on line 307 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/Role.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 307 is not covered by tests
if (!Objects.equals(this.permissions, other.permissions)) {
return false;
}
}

Check warning on line 311 in src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/Role.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 311 is not covered by tests
return true;
}
Expand Down
Loading

0 comments on commit ae39b13

Please sign in to comment.