element visible
41 | select.style.display = 'block';
42 |
43 | // remove the 'Loading folders' label
44 | loadingLabel.parentElement.removeChild(loadingLabel);
45 |
46 | // enable submitting the form
47 | document.getElementById('add-folder-role-button').removeAttribute('disabled');
48 | };
49 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/folderauth/acls/GlobalAclImpl.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth.acls;
2 |
3 | import hudson.security.Permission;
4 | import io.jenkins.plugins.folderauth.misc.PermissionWrapper;
5 | import io.jenkins.plugins.folderauth.roles.GlobalRole;
6 |
7 | import java.util.HashSet;
8 | import java.util.Set;
9 | import java.util.concurrent.ConcurrentHashMap;
10 |
11 | /**
12 | * An immutable ACL object. Dispose off when no longer valid.
13 | *
14 | * Creation of this object may be time intensive. Do NOT keep returning new instances of this object.
15 | */
16 | public class GlobalAclImpl extends AbstractAcl {
17 |
18 | /**
19 | * Initializes the ACL objects and preemptively calculates all permissions for all sids.
20 | *
21 | * @param globalRoles set of roles from which to calculate the permissions.
22 | */
23 | public GlobalAclImpl(Set globalRoles) {
24 | for (GlobalRole role : globalRoles) {
25 | Set impliedPermissions = ConcurrentHashMap.newKeySet();
26 |
27 | role.getPermissionsUnsorted().parallelStream().map(PermissionWrapper::getPermission).forEach(impliedPermissions::add);
28 |
29 | role.getSids().parallelStream().forEach(sid -> {
30 | Set permissionsForSid = permissionList.get(sid);
31 | if (permissionsForSid == null) {
32 | permissionsForSid = new HashSet<>();
33 | }
34 | permissionsForSid.addAll(impliedPermissions);
35 | permissionList.put(sid, permissionsForSid);
36 | });
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/folderauth/roles/FolderRole.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth.roles;
2 |
3 | import io.jenkins.plugins.folderauth.misc.PermissionWrapper;
4 | import org.kohsuke.stapler.DataBoundConstructor;
5 |
6 | import javax.annotation.Nonnull;
7 | import java.util.Collections;
8 | import java.util.HashSet;
9 | import java.util.Set;
10 | import java.util.TreeSet;
11 |
12 | public class FolderRole extends AbstractRole {
13 | @Nonnull
14 | private final Set folders;
15 |
16 | @DataBoundConstructor
17 | public FolderRole(String name, Set permissions, Set folders, Set sids) {
18 | super(name, permissions, sids);
19 | this.folders = new HashSet<>(folders);
20 | }
21 |
22 | public FolderRole(String name, Set permissions, Set folders) {
23 | this(name, permissions, folders, Collections.emptySet());
24 | }
25 |
26 | /**
27 | * Returns the names of the folders managed by this role
28 | *
29 | * @return the names of the folders managed by this role
30 | */
31 | @Nonnull
32 | public Set getFolderNames() {
33 | return Collections.unmodifiableSet(folders);
34 | }
35 |
36 | /**
37 | * Returns sorted folder names as a comma separated string list
38 | *
39 | * @return sorted folder names as a comma separated string list
40 | */
41 | @Nonnull
42 | @SuppressWarnings("unused") // used in index.jelly
43 | public String getFolderNamesCommaSeparated() {
44 | String csv = new TreeSet<>(folders).toString();
45 | return csv.substring(1, csv.length() - 1);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Folder-based Authorization for Jenkins
2 |
3 | [](https://ci.jenkins.io/job/Plugins/job/folder-auth-plugin/job/master/)
4 | [](https://gitter.im/jenkinsci/role-strategy-plugin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
5 | [](https://github.com/jenkinsci/folder-auth-plugin/releases/latest)
6 | [](https://dependabot.com)
7 |
8 | This plugin provides an easy way to configure permisisons inside Jenkins
9 | through *roles* which can apply to multiple users. Three types of roles are
10 | supported 'Global Roles', 'Folder Roles' and 'Agent Roles'.
11 |
12 | 
13 |
14 | To learn more about how to use this plugin, read the [docs](/docs/usage.md).
15 | We also provide REST APIs for modifying the roles, check them out [here](/docs/rest-api.adoc).
16 |
17 | ## Reach out to us
18 |
19 | You can chat with us, share your feedback and ask questions on our [Gitter chat](https://gitter.im/jenkinsci/role-strategy-plugin).
20 | We share the channel with the [Role Strategy Plugin](https://github.com/jenkinsci/role-strategy-plugin)
21 | because this plugin also follows a similar, though simplified, 'role-based' model and was created to
22 | avoid the performance penalty of using regular expressions.
23 |
24 | You can also reach out to us through Jenkins'
25 | [Developer Mailing List](mailto:jenkinsci-dev@googlegroups.com).
26 |
27 | ## Reporting issues
28 |
29 | Please create a ticket for any bug reports or feature requests on
30 | [Jenkins JIRA](https://issues.jenkins-ci.org/) and add `folder-auth-plugin`
31 | as the 'Component'.
32 |
33 | ## Changelog
34 |
35 | See [GitHub Releases](https://github.com/jenkinsci/folder-auth-plugin/releases)
36 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/folderauth/misc/PermissionFinder.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth.misc;
2 |
3 | import edu.umd.cs.findbugs.annotations.CheckForNull;
4 | import hudson.security.Permission;
5 | import hudson.security.PermissionGroup;
6 | import java.util.List;
7 | import java.util.regex.Matcher;
8 | import java.util.regex.Pattern;
9 | import org.kohsuke.accmod.Restricted;
10 | import org.kohsuke.accmod.restrictions.NoExternalUse;
11 |
12 | /**
13 | * Implements lookup for {@link Permission}s.
14 | */
15 | // Imported from https://github.com/jenkinsci/configuration-as-code-plugin/blob/727c976d137461f146b301f302d1552ca81de75e/plugin/src/main/java/io/jenkins/plugins/casc/util/PermissionFinder.java
16 | @Restricted(NoExternalUse.class)
17 | public class PermissionFinder {
18 |
19 | /** For Matrix Auth - Title/Permission **/
20 | private static final Pattern PERMISSION_PATTERN = Pattern.compile("^([^\\/]+)\\/(.+)$");
21 |
22 | /**
23 | * Attempt to match a given permission to what is defined in the UI.
24 | * @param id String of the form "Title/Permission" (Look in the UI) for a particular permission
25 | * @return a matched permission
26 | */
27 | @CheckForNull
28 | public static Permission findPermission(String id) {
29 | if (id.contains("/")) {
30 | final String resolvedId = findPermissionId(id);
31 | return resolvedId != null ? Permission.fromId(resolvedId) : null;
32 | } else {
33 | return Permission.fromId(id);
34 | }
35 | }
36 |
37 | /**
38 | * Attempt to match a given permission to what is defined in the UI.
39 | * @param id String of the form "Title/Permission" (Look in the UI) for a particular permission
40 | * @return a matched permission ID
41 | */
42 | @CheckForNull
43 | public static String findPermissionId(String id) {
44 | List pgs = PermissionGroup.getAll();
45 | Matcher m = PERMISSION_PATTERN.matcher(id);
46 | if(m.matches()) {
47 | String owner = m.group(1);
48 | String name = m.group(2);
49 | for(PermissionGroup pg : pgs) {
50 | if(pg.owner.equals(Permission.class)) {
51 | continue;
52 | }
53 | if(pg.getId().equals(owner)) {
54 | return pg.owner.getName() + "." + name;
55 | }
56 | }
57 | }
58 | return null;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/webapp/js/filter.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (() => {
4 | /**
5 | * Asynchronously filters out roles whose name matches the string entered in the text box.
6 | * @param {('agent'|'global'|'folder')} type
7 | * @returns {Promise} a promise that completes when search completes
8 | * @throws Error when unknown type is provided.
9 | */
10 | const doFilter = async (type) => {
11 | let container;
12 | switch (type) {
13 | case 'global':
14 | case 'agent':
15 | case 'folder':
16 | container = document.getElementById(`${type}RoleContainer`);
17 | break;
18 | default:
19 | throw new Error('Unknown Role type');
20 | }
21 |
22 | const str = document.getElementById(`${type}RoleFilter`).value;
23 | let matching = 0;
24 |
25 | for (let i = 0; i < container.childNodes.length; i++) {
26 | const element = container.childNodes[i];
27 | if (element.getAttribute('roleName').includes(str)) {
28 | element.style.display = 'flex';
29 | matching++;
30 | } else {
31 | element.style.display = 'none';
32 | }
33 | }
34 |
35 | const labelId = `no-matching-${type}-role-label`;
36 | const label = document.getElementById(labelId);
37 |
38 | if (!matching) {
39 | if (!label) {
40 | const newLabel = document.createElement('p');
41 | newLabel.innerText = 'No matching roles found.';
42 | newLabel.id = labelId;
43 | container.parentElement.appendChild(newLabel);
44 | }
45 | } else if (label) {
46 | container.parentElement.removeChild(label);
47 | }
48 | };
49 |
50 | // if no agent role is present, agentRoleFilter input will not be present, similarly for others
51 | const agentRoleFilter = document.getElementById('agentRoleFilter');
52 | if (agentRoleFilter) {
53 | addEventListener('input', () => doFilter('agent'));
54 | }
55 |
56 | const folderRoleFilter = document.getElementById('folderRoleFilter');
57 | if (folderRoleFilter) {
58 | folderRoleFilter.addEventListener('input', () => doFilter('folder'));
59 | }
60 |
61 | const globalRoleFilter = document.getElementById('globalRoleFilter');
62 | if (globalRoleFilter) {
63 | globalRoleFilter.addEventListener('input', () => doFilter('global'));
64 | }
65 | })();
66 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/folderauth/GlobalAclImplTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth;
2 |
3 | import com.google.common.collect.ImmutableSet;
4 | import hudson.model.Item;
5 | import hudson.model.User;
6 | import io.jenkins.plugins.folderauth.acls.GlobalAclImpl;
7 | import io.jenkins.plugins.folderauth.roles.GlobalRole;
8 | import jenkins.model.Jenkins;
9 | import org.acegisecurity.Authentication;
10 | import org.junit.Rule;
11 | import org.junit.Test;
12 | import org.jvnet.hudson.test.JenkinsRule;
13 |
14 | import java.util.HashSet;
15 | import java.util.Objects;
16 | import java.util.Set;
17 |
18 | import static io.jenkins.plugins.folderauth.misc.PermissionWrapper.wrapPermissions;
19 | import static org.junit.Assert.assertFalse;
20 | import static org.junit.Assert.assertTrue;
21 |
22 | public class GlobalAclImplTest {
23 |
24 | @Rule
25 | public JenkinsRule jenkinsRule = new JenkinsRule();
26 |
27 | @Test
28 | public void hasPermission() {
29 | jenkinsRule.jenkins.setSecurityRealm(jenkinsRule.createDummySecurityRealm());
30 | Set globalRoles = new HashSet<>();
31 |
32 | GlobalRole role1 = new GlobalRole("role1", wrapPermissions(Item.DISCOVER, Item.READ),
33 | ImmutableSet.of("foo", "bar", "baz"));
34 | GlobalRole role2 = new GlobalRole("role2", wrapPermissions(Item.READ, Item.CONFIGURE, Item.BUILD),
35 | ImmutableSet.of("baz"));
36 | GlobalRole adminRole = new GlobalRole("adminRole", wrapPermissions(Jenkins.ADMINISTER),
37 | ImmutableSet.of("admin"));
38 |
39 | globalRoles.add(role1);
40 | globalRoles.add(role2);
41 | globalRoles.add(adminRole);
42 |
43 | GlobalAclImpl acl = new GlobalAclImpl(globalRoles);
44 |
45 | Authentication foo = Objects.requireNonNull(User.getById("foo", true)).impersonate();
46 | Authentication bar = Objects.requireNonNull(User.getById("bar", true)).impersonate();
47 | Authentication baz = Objects.requireNonNull(User.getById("baz", true)).impersonate();
48 | Authentication admin = Objects.requireNonNull(User.getById("admin", true)).impersonate();
49 |
50 | assertTrue(acl.hasPermission(foo, Item.READ));
51 | assertFalse(acl.hasPermission(foo, Item.CONFIGURE));
52 | assertFalse(acl.hasPermission(foo, Jenkins.ADMINISTER));
53 |
54 | assertTrue(acl.hasPermission(bar, Item.DISCOVER));
55 | assertTrue(acl.hasPermission(baz, Item.CONFIGURE));
56 | assertFalse(acl.hasPermission(baz, Jenkins.ADMINISTER));
57 |
58 | assertTrue(acl.hasPermission(admin, Jenkins.ADMINISTER));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/webapp/js/managesids.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Assign a sid to a role.
5 | *
6 | * @param roleType {('agent' | 'global' | 'folder')} the type of the role
7 | * @param roleName the name of the role
8 | * @param sidInputBoxId id of the that contains the sid
9 | */
10 | function assignSid(roleType, roleName, sidInputBoxId) {
11 | const formData = new FormData();
12 | formData.append('sid', document.getElementById(sidInputBoxId).value);
13 | formData.append('roleName', roleName);
14 |
15 | if (!(roleType === 'agent' || roleType === 'global' || roleType === 'folder')) {
16 | throw new Error('Unknown Role Type');
17 | }
18 |
19 | const url = `${rootURL}/folder-auth/assignSidTo${roleType[0].toUpperCase()}${roleType.substring(1)}Role`;
20 | const request = new XMLHttpRequest();
21 | request.open('POST', url);
22 | request.onload = () => {
23 | if (request.status === 200) {
24 | alert('Sid added successfully.');
25 | location.reload();
26 | } else {
27 | alert('Unable to remove the sid.' + request.responseText);
28 | }
29 |
30 | };
31 |
32 | request.onerror = () => {
33 | alert('Unable to add the sid to the role: ' + request.responseText);
34 | };
35 |
36 | // see addRole.js
37 | request.setRequestHeader('Jenkins-Crumb', crumb.value);
38 | request.send(formData);
39 | }
40 |
41 | /**
42 | * Removes a sid from a role.
43 | *
44 | * @param roleType {('agent' | 'global' | 'folder')} the type of the role
45 | * @param roleName the name of the role
46 | * @param sidInputBoxId id of the that contains the sid
47 | */
48 | function removeSid(roleType, roleName, sidInputBoxId) {
49 | const formData = new FormData();
50 | formData.append('sid', document.getElementById(sidInputBoxId).value);
51 | formData.append('roleName', roleName);
52 |
53 | if (!(roleType === 'agent' || roleType === 'global' || roleType === 'folder')) {
54 | throw new Error('Unknown Role Type');
55 | }
56 |
57 | const url = `${rootURL}/folder-auth/removeSidFrom${roleType[0].toUpperCase()}${roleType.substring(1)}Role`;
58 | const request = new XMLHttpRequest();
59 | request.open('POST', url);
60 | request.onload = () => {
61 | if (request.status === 200) {
62 | alert('Sid removed successfully.');
63 | location.reload();
64 | } else {
65 | alert('Unable to remove the sid.' + request.responseText);
66 | }
67 | };
68 |
69 | request.onerror = () => {
70 | alert('Unable to remove the sid from the role: ' + request.responseText);
71 | };
72 |
73 | // see addRole.js
74 | request.setRequestHeader('Jenkins-Crumb', crumb.value);
75 | request.send(formData);
76 | }
77 |
--------------------------------------------------------------------------------
/src/test/resources/io/jenkins/plugins/folderauth/casc/config2.yml:
--------------------------------------------------------------------------------
1 | jenkins:
2 | agentProtocols:
3 | - "CLI2-connect"
4 | - "JNLP2-connect"
5 | - "JNLP3-connect"
6 | - "JNLP4-connect"
7 | - "Ping"
8 | authorizationStrategy:
9 | folderBased:
10 | globalRoles:
11 | - name: "admin"
12 | permissions:
13 | - id: "hudson.model.View.Delete"
14 | - id: "hudson.model.Computer.Connect"
15 | - id: "hudson.model.Computer.Create"
16 | - id: "hudson.model.View.Configure"
17 | - id: "hudson.model.Item.Configure"
18 | - id: "hudson.model.Computer.Build"
19 | - id: "hudson.model.Hudson.Administer"
20 | - id: "hudson.model.Item.Cancel"
21 | - id: "hudson.model.Item.Read"
22 | - id: "hudson.model.Computer.Delete"
23 | - id: "hudson.model.Item.Build"
24 | - id: "hudson.model.Item.ExtendedRead"
25 | - id: "hudson.scm.SCM.Tag"
26 | - id: "hudson.model.Item.Move"
27 | - id: "hudson.model.Item.Discover"
28 | - id: "hudson.model.Hudson.Read"
29 | - id: "hudson.model.Item.Create"
30 | - id: "hudson.model.Item.Workspace"
31 | - id: "hudson.model.Computer.Provision"
32 | - id: "hudson.model.Item.WipeOut"
33 | - id: "hudson.model.View.Read"
34 | - id: "hudson.model.View.Create"
35 | - id: "hudson.model.Item.Delete"
36 | - id: "hudson.model.Computer.ExtendedRead"
37 | - id: "hudson.model.Computer.Configure"
38 | - id: "hudson.model.Computer.Disconnect"
39 | sids:
40 | - "admin"
41 | - name: "read"
42 | permissions:
43 | - id: "hudson.model.Hudson.Read"
44 | sids:
45 | - "user1"
46 |
47 | disableRememberMe: false
48 | markupFormatter: "plainText"
49 | mode: NORMAL
50 | myViewsTabBar: "standard"
51 | numExecutors: 2
52 | primaryView:
53 | all:
54 | name: "all"
55 | projectNamingStrategy: "standard"
56 | quietPeriod: 5
57 | remotingSecurity:
58 | enabled: false
59 | scmCheckoutRetryCount: 0
60 | slaveAgentPort: 0
61 | updateCenter:
62 | sites:
63 | - id: "default"
64 | url: "http://updates.jenkins-ci.org/update-center.json"
65 |
66 | # System for test
67 | securityRealm:
68 | local:
69 | allowsSignup: false
70 | users:
71 | - id: "admin"
72 | password: "1234"
73 | - id: "user1"
74 | password: ""
75 |
76 | nodes:
77 | - dumb:
78 | mode: NORMAL
79 | name: "agent1"
80 | remoteFS: "/home/user1"
81 | launcher: jnlp
82 | - dumb:
83 | mode: NORMAL
84 | name: "agent2"
85 | remoteFS: "/home/user1"
86 | launcher: jnlp
87 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/folderauth/acls/AbstractAcl.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth.acls;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 | import hudson.security.Permission;
5 | import hudson.security.SidACL;
6 | import io.jenkins.plugins.folderauth.misc.PermissionWrapper;
7 | import jenkins.model.Jenkins;
8 | import org.acegisecurity.acls.sid.Sid;
9 | import org.apache.commons.collections.CollectionUtils;
10 |
11 | import javax.annotation.Nullable;
12 | import java.util.HashSet;
13 | import java.util.Map;
14 | import java.util.Set;
15 | import java.util.concurrent.ConcurrentHashMap;
16 |
17 | abstract class AbstractAcl extends SidACL {
18 |
19 | private static ConcurrentHashMap> implyingPermissionsCache = new ConcurrentHashMap<>();
20 |
21 | static {
22 | Permission.getAll().forEach(AbstractAcl::cacheImplyingPermissions);
23 | }
24 |
25 | private static Set cacheImplyingPermissions(Permission permission) {
26 | Set implyingPermissions;
27 |
28 | if (PermissionWrapper.DANGEROUS_PERMISSIONS.contains(permission)) {
29 | // dangerous permissions should be deferred to Jenkins.ADMINISTER
30 | implyingPermissions = getImplyingPermissions(Jenkins.ADMINISTER);
31 | } else {
32 | implyingPermissions = new HashSet<>();
33 |
34 | for (Permission p = permission; p != null; p = p.impliedBy) {
35 | implyingPermissions.add(p);
36 | }
37 | }
38 |
39 | implyingPermissionsCache.put(permission, implyingPermissions);
40 | return implyingPermissions;
41 | }
42 |
43 | private static Set getImplyingPermissions(Permission p) {
44 | Set permissions = implyingPermissionsCache.get(p);
45 | if (permissions != null) {
46 | return permissions;
47 | } else {
48 | return cacheImplyingPermissions(p);
49 | }
50 | }
51 |
52 | /**
53 | * Maps each sid to the set of permissions assigned to it.
54 | *
55 | * The implementation should ensure that this list contains accurate permissions for each sid.
56 | */
57 | protected Map> permissionList = new ConcurrentHashMap<>();
58 |
59 | @Override
60 | @SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL",
61 | justification = "hudson.security.SidACL requires null when unknown")
62 | @Nullable
63 | protected Boolean hasPermission(Sid sid, Permission permission) {
64 | if (PermissionWrapper.DANGEROUS_PERMISSIONS.contains(permission)) {
65 | permission = Jenkins.ADMINISTER;
66 | }
67 |
68 | Set permissions = permissionList.get(toString(sid));
69 | if (permissions != null && CollectionUtils.containsAny(permissions, getImplyingPermissions(permission))) {
70 | return true;
71 | }
72 |
73 | return null;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/test/resources/io/jenkins/plugins/folderauth/casc/config3.yml:
--------------------------------------------------------------------------------
1 | jenkins:
2 | agentProtocols:
3 | - "CLI2-connect"
4 | - "JNLP2-connect"
5 | - "JNLP3-connect"
6 | - "JNLP4-connect"
7 | - "Ping"
8 | authorizationStrategy:
9 | folderBased:
10 | agentRoles:
11 | - agents:
12 | - "agent1"
13 | name: "agentRole1"
14 | permissions:
15 | - id: "hudson.model.Computer.Configure"
16 | - id: "hudson.model.Computer.Disconnect"
17 | sids:
18 | - "user1"
19 | folderRoles:
20 | - folders:
21 | - "root"
22 | name: "viewRoot"
23 | permissions:
24 | - id: "hudson.model.Item.Read"
25 | sids:
26 | - "user1"
27 | globalRoles:
28 | - name: "admin"
29 | permissions:
30 | - id: "View/Delete"
31 | - id: "Agent/Connect"
32 | - id: "Agent/Create"
33 | - id: "View/Configure"
34 | - id: "Job/Configure"
35 | - id: "Agent/Build"
36 | - id: "Overall/Administer"
37 | - id: "Job/Cancel"
38 | - id: "Job/Read"
39 | - id: "Agent/Delete"
40 | - id: "Job/Build"
41 | - id: "Job/ExtendedRead"
42 | - id: "hudson.scm.SCM.Tag"
43 | - id: "Job/Move"
44 | - id: "Job/Discover"
45 | - id: "Overall/Read"
46 | - id: "Job/Create"
47 | - id: "Job/Workspace"
48 | - id: "Agent/Provision"
49 | - id: "Job/WipeOut"
50 | - id: "View/Read"
51 | - id: "View/Create"
52 | - id: "Job/Delete"
53 | - id: "Agent/ExtendedRead"
54 | - id: "Agent/Configure"
55 | - id: "Agent/Disconnect"
56 | sids:
57 | - "admin"
58 | - name: "read"
59 | permissions:
60 | - id: "hudson.model.Hudson.Read"
61 | sids:
62 | - "user1"
63 |
64 | disableRememberMe: false
65 | markupFormatter: "plainText"
66 | mode: NORMAL
67 | myViewsTabBar: "standard"
68 | numExecutors: 2
69 | primaryView:
70 | all:
71 | name: "all"
72 | projectNamingStrategy: "standard"
73 | quietPeriod: 5
74 | remotingSecurity:
75 | enabled: false
76 | scmCheckoutRetryCount: 0
77 | slaveAgentPort: 0
78 | updateCenter:
79 | sites:
80 | - id: "default"
81 | url: "http://updates.jenkins-ci.org/update-center.json"
82 |
83 | # System for test
84 | securityRealm:
85 | local:
86 | allowsSignup: false
87 | users:
88 | - id: "admin"
89 | password: "1234"
90 | - id: "user1"
91 | password: ""
92 |
93 | nodes:
94 | - dumb:
95 | mode: NORMAL
96 | name: "agent1"
97 | remoteFS: "/home/user1"
98 | launcher: jnlp
99 | - dumb:
100 | mode: NORMAL
101 | name: "agent2"
102 | remoteFS: "/home/user1"
103 | launcher: jnlp
104 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | org.jenkins-ci.plugins
6 | plugin
7 | 3.55
8 |
9 |
10 |
11 | io.jenkins.plugins
12 | folder-auth
13 | hpi
14 | ${revision}${changelist}
15 | Folder-based Authorization Strategy
16 | Manage access to Folders
17 | https://github.com/jenkinsci/folder-auth-plugin
18 |
19 |
20 |
21 | AbhyudayaSharma
22 | Abhyudaya Sharma
23 | sharmaabhyudaya@gmail.com
24 |
25 |
26 |
27 |
28 | 1.4
29 | -SNAPSHOT
30 | 2.164.1
31 | 8
32 | 1.35
33 |
34 |
35 |
36 |
37 | repo.jenkins-ci.org
38 | https://repo.jenkins-ci.org/public/
39 |
40 |
41 |
42 |
43 |
44 | repo.jenkins-ci.org
45 | https://repo.jenkins-ci.org/public/
46 |
47 |
48 |
49 |
50 |
51 | MIT License
52 | https://www.opensource.org/licenses/mit-license.php
53 | repo
54 |
55 |
56 |
57 |
58 | scm:git:ssh://github.com/jenkinsci/${project.artifactId}-plugin.git
59 |
60 | scm:git:ssh://git@github.com/jenkinsci/${project.artifactId}-plugin.git
61 |
62 | https://github.com/jenkinsci/folder-auth-plugin
63 | ${scmTag}
64 |
65 |
66 |
67 |
68 | org.jenkins-ci.plugins
69 | cloudbees-folder
70 | 6.4
71 |
72 |
73 | io.jenkins
74 | configuration-as-code
75 | ${configuration-as-code.version}
76 | true
77 |
78 |
79 | io.jenkins.configuration-as-code
80 | test-harness
81 | ${configuration-as-code.version}
82 | test
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/src/test/resources/io/jenkins/plugins/folderauth/casc/config.yml:
--------------------------------------------------------------------------------
1 | jenkins:
2 | agentProtocols:
3 | - "CLI2-connect"
4 | - "JNLP2-connect"
5 | - "JNLP3-connect"
6 | - "JNLP4-connect"
7 | - "Ping"
8 | authorizationStrategy:
9 | folderBased:
10 | agentRoles:
11 | - agents:
12 | - "agent1"
13 | name: "agentRole1"
14 | permissions:
15 | - id: "hudson.model.Computer.Configure"
16 | - id: "hudson.model.Computer.Disconnect"
17 | sids:
18 | - "user1"
19 | folderRoles:
20 | - folders:
21 | - "root"
22 | name: "viewRoot"
23 | permissions:
24 | - id: "hudson.model.Item.Read"
25 | sids:
26 | - "user1"
27 | globalRoles:
28 | - name: "admin"
29 | permissions:
30 | - id: "hudson.model.View.Delete"
31 | - id: "hudson.model.Computer.Connect"
32 | - id: "hudson.model.Computer.Create"
33 | - id: "hudson.model.View.Configure"
34 | - id: "hudson.model.Item.Configure"
35 | - id: "hudson.model.Computer.Build"
36 | - id: "hudson.model.Hudson.Administer"
37 | - id: "hudson.model.Item.Cancel"
38 | - id: "hudson.model.Item.Read"
39 | - id: "hudson.model.Computer.Delete"
40 | - id: "hudson.model.Item.Build"
41 | - id: "hudson.model.Item.ExtendedRead"
42 | - id: "hudson.scm.SCM.Tag"
43 | - id: "hudson.model.Item.Move"
44 | - id: "hudson.model.Item.Discover"
45 | - id: "hudson.model.Hudson.Read"
46 | - id: "hudson.model.Item.Create"
47 | - id: "hudson.model.Item.Workspace"
48 | - id: "hudson.model.Computer.Provision"
49 | - id: "hudson.model.Item.WipeOut"
50 | - id: "hudson.model.View.Read"
51 | - id: "hudson.model.View.Create"
52 | - id: "hudson.model.Item.Delete"
53 | - id: "hudson.model.Computer.ExtendedRead"
54 | - id: "hudson.model.Computer.Configure"
55 | - id: "hudson.model.Computer.Disconnect"
56 | sids:
57 | - "admin"
58 | - name: "read"
59 | permissions:
60 | - id: "hudson.model.Hudson.Read"
61 | sids:
62 | - "user1"
63 |
64 | disableRememberMe: false
65 | markupFormatter: "plainText"
66 | mode: NORMAL
67 | myViewsTabBar: "standard"
68 | numExecutors: 2
69 | primaryView:
70 | all:
71 | name: "all"
72 | projectNamingStrategy: "standard"
73 | quietPeriod: 5
74 | remotingSecurity:
75 | enabled: false
76 | scmCheckoutRetryCount: 0
77 | slaveAgentPort: 0
78 | updateCenter:
79 | sites:
80 | - id: "default"
81 | url: "http://updates.jenkins-ci.org/update-center.json"
82 |
83 | # System for test
84 | securityRealm:
85 | local:
86 | allowsSignup: false
87 | users:
88 | - id: "admin"
89 | password: "1234"
90 | - id: "user1"
91 | password: ""
92 |
93 | nodes:
94 | - dumb:
95 | mode: NORMAL
96 | name: "agent1"
97 | remoteFS: "/home/user1"
98 | launcher: jnlp
99 | - dumb:
100 | mode: NORMAL
101 | name: "agent2"
102 | remoteFS: "/home/user1"
103 | launcher: jnlp
104 |
--------------------------------------------------------------------------------
/src/main/webapp/css/folder-strategy.css:
--------------------------------------------------------------------------------
1 | div.form-row {
2 | padding-top: 5px;
3 | padding-bottom: 10px;
4 | padding-right: 10px;
5 | }
6 |
7 | .form-label {
8 | padding-right: 20px;
9 | padding-bottom: 10px;
10 | font-size: larger;
11 | font-weight: bold;
12 | }
13 |
14 | div.permission-row {
15 | padding: 5px 0;
16 | }
17 |
18 | div.role-container {
19 | display: flex;
20 | flex-direction: row;
21 | flex-basis: auto;
22 | flex-wrap: wrap;
23 | justify-content: flex-start;
24 | max-height: 525px;
25 | overflow-y: scroll;
26 | }
27 |
28 | .loading {
29 | padding-top: 7px;
30 | font-size: 12px;
31 | font-weight: lighter;
32 | }
33 |
34 | div.role {
35 | display: flex;
36 | flex-direction: column;
37 | padding: 10px;
38 | border-width: thin;
39 | border-radius: 5px;
40 | border-style: solid;
41 | border-color: black;
42 | background-color: #fdfdfd;
43 | margin: 3px 3px;
44 | position: relative;
45 | }
46 |
47 | .delete-role {
48 | position: absolute;
49 | top: 5px;
50 | right: 5px;
51 | border-style: solid;
52 | -webkit-border-radius: 5px;
53 | -moz-border-radius: 5px;
54 | border-radius: 5px;
55 | border-color: #fff;
56 | background-color: red;
57 | color: white;
58 | padding: 2px 5px;
59 | }
60 |
61 | .delete-role:hover {
62 | background-color: firebrick;
63 | }
64 |
65 | input[type="text"] {
66 | margin-left: 3px;
67 | border-radius: 5px;
68 | border-style: solid;
69 | border-width: thin;
70 | padding: 3px 3px;
71 | }
72 |
73 | button.submit-button, input.submit {
74 | -webkit-border-radius: 5px;
75 | -moz-border-radius: 5px;
76 | border-radius: 5px;
77 | padding: 5px 5px;
78 | margin-top: 5px;
79 | margin-left: 10px;
80 | line-height: 1.25;
81 | border-style: solid;
82 | border-color: #007bff;
83 | background-color: #007bff;
84 | color: #ffffff;
85 | display: inline-block;
86 | }
87 |
88 | button.submit-button:hover, input.submit:hover {
89 | background-color: #0069d9;
90 | }
91 |
92 | button.submit-button:active, input.submit:hover {
93 | background-color: #0062cc;
94 | }
95 |
96 | input.filter {
97 | margin-bottom: 10px;
98 | padding: 5px 5px;
99 | -webkit-border-radius: 5px;
100 | -moz-border-radius: 5px;
101 | border-radius: 5px;
102 | border-style: solid;
103 | border-width: thin;
104 | width: 100%;
105 | max-width: 250px;
106 | }
107 |
108 | select[multiple] {
109 | border-color: #000;
110 | -webkit-border-radius: 10px;
111 | -moz-border-radius: 10px;
112 | border-radius: 10px;
113 | border-style: solid;
114 | border-width: thin;
115 | padding: 10px;
116 | scroll-padding: 10px;
117 | display: block;
118 | margin: 10px;
119 | width: content-box;
120 | }
121 |
122 | /* Taken from https://www.w3schools.com/howto/howto_js_collapsible.asp */
123 |
124 | .collapsible {
125 | background-color: #eee;
126 | color: #444;
127 | cursor: pointer;
128 | padding: 5px;
129 | width: 100%;
130 | border: none;
131 | text-align: left;
132 | outline: none;
133 | font-size: inherit;
134 | }
135 |
136 | .active, .collapsible:hover {
137 | background-color: #ccc;
138 | }
139 |
140 | .collapsible-content {
141 | padding: 0 18px;
142 | display: none;
143 | overflow-x: hidden;
144 | overflow-y: scroll;
145 | background-color: #f1f1f1;
146 | transition: max-height 0.2s ease-out;
147 | min-height: 125px;
148 | max-height: 125px;
149 | }
150 |
151 | .center {
152 | justify-content: center;
153 | }
154 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/folderauth/roles/AbstractRole.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth.roles;
2 |
3 | import io.jenkins.plugins.folderauth.misc.PermissionWrapper;
4 | import org.kohsuke.accmod.Restricted;
5 | import org.kohsuke.accmod.restrictions.NoExternalUse;
6 |
7 | import javax.annotation.Nonnull;
8 | import java.util.Collections;
9 | import java.util.HashSet;
10 | import java.util.Objects;
11 | import java.util.Set;
12 | import java.util.SortedSet;
13 | import java.util.TreeSet;
14 |
15 | /**
16 | * A role as an immutable object
17 | */
18 | @Restricted(NoExternalUse.class)
19 | public abstract class AbstractRole implements Comparable {
20 | @Override
21 | public int compareTo(@Nonnull AbstractRole other) {
22 | return this.name.compareTo(other.name);
23 | }
24 |
25 | /**
26 | * The unique name of the role.
27 | */
28 | @Nonnull
29 | protected final String name;
30 |
31 | /**
32 | * Wrappers of permissions that are assigned to this role. Should not be modified.
33 | */
34 | @Nonnull
35 | private final Set permissionWrappers;
36 |
37 | /**
38 | * The sids on which this role is applicable.
39 | */
40 | @Nonnull
41 | protected final Set sids;
42 |
43 | AbstractRole(String name, Set permissions, Set sids) {
44 | this.name = name;
45 | this.permissionWrappers = new HashSet<>(permissions);
46 | this.sids = new HashSet<>(sids);
47 | }
48 |
49 | @Override
50 | public boolean equals(Object o) {
51 | if (this == o) return true;
52 | if (o == null || getClass() != o.getClass()) return false;
53 | AbstractRole role = (AbstractRole) o;
54 | return name.equals(role.name) &&
55 | permissionWrappers.equals(role.permissionWrappers) &&
56 | sids.equals(role.sids);
57 | }
58 |
59 | @Override
60 | public int hashCode() {
61 | return Objects.hash(name, permissionWrappers, sids);
62 | }
63 |
64 | /**
65 | * The name of the Role
66 | *
67 | * @return the name of the role
68 | */
69 | @Nonnull
70 | public String getName() {
71 | return name;
72 | }
73 |
74 | /**
75 | * The permissions assigned to the role.
76 | *
77 | * This method, however, does not return all permissions implied by this {@link AbstractRole}
78 | *
79 | * @return the permissions assigned to the role.
80 | * @see AbstractRole#getPermissionsUnsorted() when the permissions are not needed in a sorted order.
81 | */
82 | @Nonnull
83 | public SortedSet getPermissions() {
84 | return Collections.unmodifiableSortedSet(new TreeSet<>(permissionWrappers));
85 | }
86 |
87 | /**
88 | * The permissions assigned to the role in an unsorted order.
89 | *
90 | * @return permissions in an unsorted order.
91 | * @see AbstractRole#getPermissions() when permissions are needed in a sorted order.
92 | */
93 | @Nonnull
94 | public Set getPermissionsUnsorted() {
95 | return Collections.unmodifiableSet(permissionWrappers);
96 | }
97 |
98 | /**
99 | * List of sids on which the role is applicable.
100 | *
101 | * @return list of sids on which this role is applicable.
102 | */
103 | @Nonnull
104 | public Set getSids() {
105 | return Collections.unmodifiableSet(sids);
106 | }
107 |
108 | /**
109 | * Return a sorted comma separated list of sids assigned to this role
110 | *
111 | * @return a sorted comma separated list of sids assigned to this role
112 | */
113 | @Nonnull
114 | @SuppressWarnings("unused") // used by index.jelly
115 | public String getSidsCommaSeparated() {
116 | String string = new TreeSet<>(sids).toString();
117 | return string.substring(1, string.length() - 1);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/webapp/js/addrole.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // noinspection JSUnusedGlobalSymbols
4 | /**
5 | * Adds a global role
6 | */
7 | const addGlobalRole = () => {
8 | const roleName = document.getElementById('globalRoleName').value;
9 | if (!roleName || roleName.length < 3) {
10 | alert('Please enter a valid name for the role to be added.');
11 | return;
12 | }
13 |
14 | const response = {
15 | name: roleName,
16 | permissions: document.getElementById('global-permission-select').getValue(),
17 | };
18 |
19 | if (response.permissions.length <= 0) {
20 | alert('Please select at least one permission');
21 | return;
22 | }
23 |
24 | sendPostRequest(`${rootURL}/folder-auth/addGlobalRole`, response);
25 | };
26 |
27 | // noinspection JSUnusedGlobalSymbols
28 | /**
29 | * Adds a Folder Role
30 | */
31 | const addFolderRole = () => {
32 | const roleName = document.getElementById('folderRoleName').value;
33 | if (!roleName || roleName.length < 3) {
34 | alert('Please enter a valid name for the role to be added');
35 | return;
36 | }
37 |
38 | const response = {
39 | name: roleName,
40 | permissions: document.getElementById('folder-permission-select').getValue(),
41 | folderNames: document.getElementById('folder-select').getValue(),
42 | };
43 |
44 | if (!response.permissions || response.permissions.length <= 0) {
45 | alert('Please select at least one permission');
46 | return;
47 | }
48 |
49 | if (!response.folderNames || response.folderNames.length <= 0) {
50 | alert('Please select at least one folder on which this role will be applicable');
51 | return;
52 | }
53 |
54 | sendPostRequest(`${rootURL}/folder-auth/addFolderRole`, response);
55 | };
56 |
57 | // noinspection JSUnusedGlobalSymbols
58 | /**
59 | * Adds an agent Role
60 | */
61 | const addAgentRole = () => {
62 | const roleName = document.getElementById('agentRoleName').value;
63 | if (!roleName || roleName.length < 3) {
64 | alert('Please enter a valid name for the role to be added');
65 | return;
66 | }
67 |
68 | const response = {
69 | name: roleName,
70 | agentNames: document.getElementById('agent-select').getValue(),
71 | permissions: document.getElementById('agent-permission-select').getValue(),
72 | };
73 |
74 | if (!response.permissions || response.permissions.length <= 0) {
75 | alert('Please select at least one permission');
76 | return;
77 | }
78 |
79 | if (!response.agentNames || response.agentNames.length <= 0) {
80 | alert('Please select at least one agent on which this role will be applicable');
81 | return;
82 | }
83 |
84 | sendPostRequest(`${rootURL}/folder-auth/addAgentRole`, response);
85 | };
86 |
87 | /**
88 | * Sends a POST request to {@code postUrl}
89 | * @param postUrl the URL
90 | * @param json JSON data to be sent
91 | */
92 | const sendPostRequest = (postUrl, json) => {
93 | const xhr = new XMLHttpRequest();
94 | xhr.open('POST', postUrl, true);
95 | xhr.setRequestHeader('Content-Type', 'application/json');
96 | // Jelly file sets up the crumb value for CSRF protection
97 | if (crumb.value) {
98 | xhr.setRequestHeader('Jenkins-Crumb', crumb.value);
99 | }
100 |
101 | xhr.onload = () => {
102 | if (xhr.status === 200) {
103 | alert('The role was added successfully');
104 | location.reload(); // refresh the page
105 | } else {
106 | alert('Unable to add the role\n' + xhr.responseText);
107 | }
108 | };
109 |
110 | // this is really bad.
111 | // See https://github.com/jenkinsci/jenkins/blob/75468da366c1d257a51655dcbe952d55b8aeeb9c/war/src/main/js/util/jenkins.js#L22
112 | const oldPrototype = Array.prototype.toJSON;
113 | delete Array.prototype.toJSON;
114 |
115 | try {
116 | xhr.send(JSON.stringify(json));
117 | } finally {
118 | Array.prototype.toJSON = oldPrototype;
119 | }
120 | };
121 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/folderauth/casc/ConfigurationAsCodeTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth.casc;
2 |
3 | import com.cloudbees.hudson.plugins.folder.Folder;
4 | import hudson.model.Computer;
5 | import hudson.model.Item;
6 | import hudson.model.User;
7 | import hudson.security.ACL;
8 | import hudson.security.ACLContext;
9 | import hudson.slaves.DumbSlave;
10 | import hudson.slaves.JNLPLauncher;
11 | import io.jenkins.plugins.casc.ConfigurationContext;
12 | import io.jenkins.plugins.casc.ConfiguratorRegistry;
13 | import io.jenkins.plugins.casc.misc.ConfiguredWithCode;
14 | import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule;
15 | import io.jenkins.plugins.casc.model.CNode;
16 | import jenkins.model.Jenkins;
17 | import org.junit.Before;
18 | import org.junit.Rule;
19 | import org.junit.Test;
20 |
21 | import java.util.Objects;
22 |
23 | import static io.jenkins.plugins.casc.misc.Util.getJenkinsRoot;
24 | import static io.jenkins.plugins.casc.misc.Util.toStringFromYamlFile;
25 | import static io.jenkins.plugins.casc.misc.Util.toYamlString;
26 | import static org.hamcrest.MatcherAssert.assertThat;
27 | import static org.hamcrest.core.Is.is;
28 | import static org.junit.Assert.assertFalse;
29 | import static org.junit.Assert.assertTrue;
30 |
31 | public class ConfigurationAsCodeTest {
32 | private Folder folder;
33 |
34 | @Rule
35 | public JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule();
36 |
37 | @Before
38 | public void setUp() throws Exception {
39 | j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
40 | j.jenkins.addNode(new DumbSlave("agent1", "", new JNLPLauncher(true)));
41 | folder = j.jenkins.createProject(Folder.class, "root");
42 | }
43 |
44 | @Test
45 | @ConfiguredWithCode("config.yml")
46 | public void configurationImportTest() {
47 | try (ACLContext ignored = ACL.as(User.getOrCreateByIdOrFullName("admin"))) {
48 | assertTrue(j.jenkins.hasPermission(Jenkins.ADMINISTER));
49 | }
50 |
51 | try (ACLContext ignored = ACL.as(User.getOrCreateByIdOrFullName("user1"))) {
52 | assertTrue(folder.hasPermission(Item.READ));
53 | assertFalse(j.jenkins.hasPermission(Jenkins.ADMINISTER));
54 |
55 | assertTrue(Objects.requireNonNull(j.jenkins.getComputer("agent1")).hasPermission(Computer.CONFIGURE));
56 | assertFalse(Objects.requireNonNull(j.jenkins.getComputer("agent1")).hasPermission(Computer.DELETE));
57 | }
58 | }
59 |
60 | @Test
61 | @ConfiguredWithCode("config3.yml")
62 | public void configurationImportWithHumanReadableTest() {
63 | try (ACLContext ignored = ACL.as(User.getOrCreateByIdOrFullName("admin"))) {
64 | assertTrue(j.jenkins.hasPermission(Jenkins.ADMINISTER));
65 | }
66 |
67 | try (ACLContext ignored = ACL.as(User.getOrCreateByIdOrFullName("user1"))) {
68 | assertTrue(folder.hasPermission(Item.READ));
69 | assertFalse(j.jenkins.hasPermission(Jenkins.ADMINISTER));
70 |
71 | assertTrue(Objects.requireNonNull(j.jenkins.getComputer("agent1")).hasPermission(Computer.CONFIGURE));
72 | assertFalse(Objects.requireNonNull(j.jenkins.getComputer("agent1")).hasPermission(Computer.DELETE));
73 | }
74 | }
75 |
76 | @Test
77 | @ConfiguredWithCode("config.yml")
78 | public void configurationExportTest() throws Exception {
79 | ConfiguratorRegistry registry = ConfiguratorRegistry.get();
80 | ConfigurationContext context = new ConfigurationContext(registry);
81 | CNode yourAttribute = getJenkinsRoot(context).get("authorizationStrategy").asMapping()
82 | .get("folderBased");
83 |
84 | String exported = toYamlString(yourAttribute);
85 | String expected = toStringFromYamlFile(this, "expected.yml");
86 |
87 | assertThat(exported, is(expected));
88 | }
89 |
90 | @Test
91 | @ConfiguredWithCode("config3.yml")
92 | public void configurationExportWithHumanReadableTest() throws Exception {
93 | ConfiguratorRegistry registry = ConfiguratorRegistry.get();
94 | ConfigurationContext context = new ConfigurationContext(registry);
95 | CNode yourAttribute = getJenkinsRoot(context).get("authorizationStrategy").asMapping()
96 | .get("folderBased");
97 |
98 | String exported = toYamlString(yourAttribute);
99 | String expected = toStringFromYamlFile(this, "expected3.yml");
100 |
101 | assertThat(exported, is(expected));
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/folderauth/jmh/benchmarks/GlobalRoleBenchmark.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth.jmh.benchmarks;
2 |
3 | import com.google.common.collect.ImmutableSet;
4 | import hudson.model.Item;
5 | import hudson.model.User;
6 | import io.jenkins.plugins.folderauth.FolderBasedAuthorizationStrategy;
7 | import io.jenkins.plugins.folderauth.acls.GlobalAclImpl;
8 | import io.jenkins.plugins.folderauth.roles.GlobalRole;
9 | import jenkins.benchmark.jmh.JmhBenchmark;
10 | import jenkins.benchmark.jmh.JmhBenchmarkState;
11 | import org.acegisecurity.context.SecurityContext;
12 | import org.acegisecurity.context.SecurityContextHolder;
13 | import org.jvnet.hudson.test.JenkinsRule;
14 | import org.openjdk.jmh.annotations.Benchmark;
15 | import org.openjdk.jmh.annotations.Level;
16 | import org.openjdk.jmh.annotations.Scope;
17 | import org.openjdk.jmh.annotations.Setup;
18 | import org.openjdk.jmh.annotations.State;
19 | import org.openjdk.jmh.infra.Blackhole;
20 |
21 | import java.util.Collections;
22 | import java.util.HashSet;
23 | import java.util.Objects;
24 | import java.util.Set;
25 |
26 | import static io.jenkins.plugins.folderauth.misc.PermissionWrapper.wrapPermissions;
27 | import static org.junit.Assert.assertFalse;
28 |
29 | /**
30 | * This benchmark is created to test the performance of GlobalRoles. This test is inspired from
31 | * https://github.com/jenkinsci/role-strategy-plugin/blob/master/src/test/java/jmh/benchmarks/RoleMapBenchmark.java .
32 | *
33 | * This tests the scalability of the performance of {@link GlobalAclImpl} with increased number of roles.
34 | * Do note that "user3" does not have the {@link Item#CREATE} permission.
35 | */
36 | @JmhBenchmark
37 | @SuppressWarnings("unused")
38 | public class GlobalRoleBenchmark {
39 | public static class GlobalRoles050 extends GlobalRoleBenchmarkState {
40 | @Override
41 | int getRoleCount() {
42 | return 50;
43 | }
44 | }
45 |
46 | public static class GlobalRoles100 extends GlobalRoleBenchmarkState {
47 | @Override
48 | int getRoleCount() {
49 | return 100;
50 | }
51 | }
52 |
53 | public static class GlobalRoles200 extends GlobalRoleBenchmarkState {
54 | @Override
55 | int getRoleCount() {
56 | return 200;
57 | }
58 | }
59 |
60 | public static class GlobalRoles500 extends GlobalRoleBenchmarkState {
61 | @Override
62 | int getRoleCount() {
63 | return 500;
64 | }
65 | }
66 |
67 | @State(Scope.Thread)
68 | public static class ThreadState {
69 | @Setup(Level.Iteration)
70 | public void setup() {
71 | SecurityContext holder = SecurityContextHolder.getContext();
72 | holder.setAuthentication(Objects.requireNonNull(User.getById("user3", true)).impersonate());
73 | }
74 | }
75 |
76 | @Benchmark
77 | public void benchmark050(GlobalRoles050 state, ThreadState threadState, Blackhole blackhole) {
78 | assertFalse(state.acl.hasPermission(Item.CREATE));
79 | }
80 |
81 | @Benchmark
82 | public void benchmark100(GlobalRoles100 state, ThreadState threadState, Blackhole blackhole) {
83 | blackhole.consume(state.acl.hasPermission(Item.CREATE));
84 | }
85 |
86 | @Benchmark
87 | public void benchmark200(GlobalRoles200 state, ThreadState threadState, Blackhole blackhole) {
88 | blackhole.consume(state.acl.hasPermission(Item.CREATE));
89 | }
90 |
91 | @Benchmark
92 | public void benchmark500(GlobalRoles500 state, ThreadState threadState, Blackhole blackhole) {
93 | blackhole.consume(state.acl.hasPermission(Item.CREATE));
94 | }
95 | }
96 |
97 | abstract class GlobalRoleBenchmarkState extends JmhBenchmarkState {
98 |
99 | GlobalAclImpl acl;
100 |
101 | @Override
102 | public void setup() {
103 | getJenkins().setSecurityRealm(new JenkinsRule().createDummySecurityRealm());
104 | Set globalRoles = new HashSet<>();
105 | for (int i = 0; i < getRoleCount(); i++) {
106 | globalRoles.add(new GlobalRole("role" + i, wrapPermissions(Item.DISCOVER, Item.CONFIGURE),
107 | ImmutableSet.of("user" + i)));
108 | }
109 |
110 | FolderBasedAuthorizationStrategy strategy = new FolderBasedAuthorizationStrategy(
111 | globalRoles, Collections.emptySet(), Collections.emptySet());
112 | acl = strategy.getRootACL();
113 | assertFalse(acl.hasPermission(Objects.requireNonNull(User.getById("user3", true)).impersonate(),
114 | Item.CREATE));
115 | }
116 |
117 | abstract int getRoleCount();
118 | }
119 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/folderauth/RestartSurvivabilityTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth;
2 |
3 | import com.cloudbees.hudson.plugins.folder.Folder;
4 | import com.google.common.collect.ImmutableSet;
5 | import hudson.model.Computer;
6 | import hudson.model.Item;
7 | import hudson.model.User;
8 | import hudson.security.ACL;
9 | import hudson.security.ACLContext;
10 | import hudson.security.AuthorizationStrategy;
11 | import hudson.util.XStream2;
12 | import io.jenkins.plugins.folderauth.roles.AgentRole;
13 | import io.jenkins.plugins.folderauth.roles.FolderRole;
14 | import io.jenkins.plugins.folderauth.roles.GlobalRole;
15 | import jenkins.model.Jenkins;
16 | import org.junit.Rule;
17 | import org.junit.Test;
18 | import org.junit.runners.model.Statement;
19 | import org.jvnet.hudson.test.Issue;
20 | import org.jvnet.hudson.test.RestartableJenkinsRule;
21 |
22 | import java.util.Collections;
23 | import java.util.HashSet;
24 | import java.util.Set;
25 |
26 | import static io.jenkins.plugins.folderauth.misc.PermissionWrapper.wrapPermissions;
27 | import static org.junit.Assert.assertEquals;
28 | import static org.junit.Assert.assertFalse;
29 | import static org.junit.Assert.assertNotNull;
30 | import static org.junit.Assert.assertTrue;
31 |
32 | public class RestartSurvivabilityTest {
33 | @Rule
34 | public RestartableJenkinsRule rule = new RestartableJenkinsRule();
35 |
36 | @Test
37 | @Issue("JENKINS-58485")
38 | public void shouldHaveSameConfigurationAfterRestart() {
39 | rule.addStep(new Statement() {
40 | @Override
41 | public void evaluate() throws Exception {
42 | rule.j.createProject(Folder.class, "folder");
43 | rule.j.jenkins.setSecurityRealm(rule.j.createDummySecurityRealm());
44 | rule.j.jenkins.setAuthorizationStrategy(createNewFolderBasedAuthorizationStrategy());
45 | rule.j.jenkins.addNode(rule.j.createSlave("foo", null, null));
46 | checkConfiguration();
47 | }
48 | });
49 |
50 | rule.addStep(new Statement() {
51 | @Override
52 | public void evaluate() {
53 | rule.j.jenkins.setSecurityRealm(rule.j.createDummySecurityRealm());
54 | checkConfiguration();
55 |
56 | // JENKINS-58485
57 | XStream2 xStream = new XStream2();
58 | String xml = xStream.toXML(rule.j.jenkins.getAuthorizationStrategy());
59 | assertFalse(xml.contains("ConcurrentHashMap$KeySetView"));
60 | }
61 | });
62 | }
63 |
64 | private FolderBasedAuthorizationStrategy createNewFolderBasedAuthorizationStrategy() {
65 | Set globalRoles = new HashSet<>();
66 | globalRoles.add(new GlobalRole("admin", wrapPermissions(Jenkins.ADMINISTER), ImmutableSet.of("admin")));
67 | globalRoles.add(new GlobalRole("read", wrapPermissions(Jenkins.READ), ImmutableSet.of("authenticated")));
68 |
69 | Set folderRoles = new HashSet<>();
70 | folderRoles.add(new FolderRole("read", wrapPermissions(Item.READ), ImmutableSet.of("folder"),
71 | ImmutableSet.of("user1")));
72 |
73 | Set agentRoles = new HashSet<>();
74 | agentRoles.add(new AgentRole("configureMaster", wrapPermissions(Computer.CONFIGURE),
75 | Collections.singleton("foo"), Collections.singleton("user1")));
76 |
77 | return new FolderBasedAuthorizationStrategy(globalRoles, folderRoles, agentRoles);
78 | }
79 |
80 | private void checkConfiguration() {
81 | Jenkins jenkins = Jenkins.get();
82 | try (ACLContext ignored = ACL.as(User.getById("admin", true))) {
83 | assertTrue(jenkins.hasPermission(Jenkins.ADMINISTER));
84 | }
85 |
86 | try (ACLContext ignored = ACL.as(User.getById("user1", true))) {
87 | Folder folder = (Folder) jenkins.getItem("folder");
88 | assertNotNull(folder);
89 | assertTrue(jenkins.hasPermission(Jenkins.READ));
90 | assertTrue(folder.hasPermission(Item.READ));
91 | assertFalse(folder.hasPermission(Item.CONFIGURE));
92 | assertFalse(jenkins.hasPermission(Jenkins.ADMINISTER));
93 |
94 | Computer computer = jenkins.getComputer("foo");
95 | assertNotNull(computer);
96 | assertTrue(computer.hasPermission(Computer.CONFIGURE));
97 | assertFalse(computer.hasPermission(Computer.DELETE));
98 | }
99 |
100 | AuthorizationStrategy a = Jenkins.get().getAuthorizationStrategy();
101 | assertTrue(a instanceof FolderBasedAuthorizationStrategy);
102 | FolderBasedAuthorizationStrategy strategy = (FolderBasedAuthorizationStrategy) a;
103 | assertEquals(strategy.getGlobalRoles().size(), 2);
104 | assertEquals(strategy.getFolderRoles().size(), 1);
105 | assertEquals(strategy.getAgentRoles().size(), 1);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/folderauth/jmh/benchmarks/FolderRoleBenchmark.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth.jmh.benchmarks;
2 |
3 | import com.cloudbees.hudson.plugins.folder.Folder;
4 | import com.google.common.collect.ImmutableSet;
5 | import hudson.model.FreeStyleProject;
6 | import hudson.model.Item;
7 | import hudson.model.User;
8 | import io.jenkins.plugins.folderauth.FolderBasedAuthorizationStrategy;
9 | import io.jenkins.plugins.folderauth.roles.FolderRole;
10 | import io.jenkins.plugins.folderauth.roles.GlobalRole;
11 | import jenkins.benchmark.jmh.JmhBenchmark;
12 | import jenkins.benchmark.jmh.JmhBenchmarkState;
13 | import jenkins.model.Jenkins;
14 | import org.acegisecurity.context.SecurityContext;
15 | import org.acegisecurity.context.SecurityContextHolder;
16 | import org.jvnet.hudson.test.JenkinsRule;
17 | import org.openjdk.jmh.annotations.Benchmark;
18 | import org.openjdk.jmh.annotations.Level;
19 | import org.openjdk.jmh.annotations.Scope;
20 | import org.openjdk.jmh.annotations.Setup;
21 | import org.openjdk.jmh.annotations.State;
22 | import org.openjdk.jmh.infra.Blackhole;
23 |
24 | import java.util.Collections;
25 | import java.util.HashSet;
26 | import java.util.Objects;
27 | import java.util.Random;
28 | import java.util.Set;
29 |
30 | import static io.jenkins.plugins.folderauth.misc.PermissionWrapper.wrapPermissions;
31 |
32 | /**
33 | * Benchmarks for {@link FolderRole}s based on the configuration of
34 | * https://github.com/jenkinsci/role-strategy-plugin/blob/master/src/test/java/jmh/benchmarks/FolderAccessBenchmark.java
35 | */
36 | @JmhBenchmark
37 | public class FolderRoleBenchmark {
38 | public static class MyState extends JmhBenchmarkState {
39 |
40 | @Override
41 | public void setup() throws Exception {
42 | Jenkins jenkins = getJenkins();
43 | jenkins.setSecurityRealm(new JenkinsRule().createDummySecurityRealm());
44 |
45 | Set globalRoles = ImmutableSet.of((
46 | new GlobalRole("ADMIN", wrapPermissions(Jenkins.ADMINISTER), Collections.singleton("admin"))),
47 | new GlobalRole("read", wrapPermissions(Jenkins.READ), Collections.singleton("authenticated"))
48 | );
49 |
50 | Set folderRoles = new HashSet<>();
51 |
52 | Random random = new Random(100L);
53 |
54 | // create the folders
55 | for (int i = 0; i < 10; i++) {
56 | String topFolderName = "TopFolder" + i;
57 | Folder folder = jenkins.createProject(Folder.class, topFolderName);
58 |
59 | Set users = new HashSet<>();
60 | for (int k = 0; k < random.nextInt(5); k++) {
61 | users.add("user" + random.nextInt(100));
62 | }
63 |
64 | FolderRole userRole = new FolderRole(topFolderName, wrapPermissions(Item.READ, Item.DISCOVER),
65 | Collections.singleton(topFolderName), users);
66 |
67 | folderRoles.add(userRole);
68 |
69 | for (int j = 0; j < 5; j++) {
70 | Folder bottom = folder.createProject(Folder.class, "BottomFolder" + j);
71 |
72 | Set maintainers = new HashSet<>(2);
73 | maintainers.add("user" + random.nextInt(100));
74 | maintainers.add("user" + random.nextInt(100));
75 |
76 | FolderRole maintainerRole = new FolderRole(bottom.getFullName(),
77 | wrapPermissions(Item.READ, Item.DISCOVER, Item.CREATE),
78 | Collections.singleton(topFolderName), maintainers);
79 |
80 | Set admin = Collections.singleton("user" + random.nextInt(100));
81 |
82 | FolderRole folderAdminRole = new FolderRole(bottom.getFullName(), wrapPermissions(Item.READ, Item.DISCOVER,
83 | Item.CONFIGURE, Item.CREATE), Collections.singleton(topFolderName), admin);
84 | folderRoles.add(maintainerRole);
85 | folderRoles.add(folderAdminRole);
86 |
87 | for (int k = 0; k < 5; k++) {
88 | bottom.createProject(FreeStyleProject.class, "Project" + k);
89 | }
90 | }
91 | }
92 |
93 | jenkins.setAuthorizationStrategy(new FolderBasedAuthorizationStrategy(globalRoles, folderRoles,
94 | Collections.emptySet()));
95 | }
96 | }
97 |
98 | @State(Scope.Thread)
99 | public static class ThreadState {
100 | @Setup(Level.Iteration)
101 | public void setup() {
102 | SecurityContext securityContext = SecurityContextHolder.getContext();
103 | securityContext.setAuthentication(Objects.requireNonNull(User.getById("user33", true)).impersonate());
104 | }
105 | }
106 |
107 | @Benchmark
108 | public void renderViewSimulation(MyState state, ThreadState threadState, Blackhole blackhole) {
109 | blackhole.consume(state.getJenkins().getAllItems());
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/folderauth/misc/PermissionWrapper.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth.misc;
2 |
3 | import hudson.PluginManager;
4 | import hudson.security.Permission;
5 | import io.jenkins.plugins.folderauth.Messages;
6 | import io.jenkins.plugins.folderauth.roles.AbstractRole;
7 | import jenkins.model.Jenkins;
8 | import org.kohsuke.accmod.Restricted;
9 | import org.kohsuke.accmod.restrictions.NoExternalUse;
10 | import org.kohsuke.stapler.DataBoundConstructor;
11 |
12 | import javax.annotation.Nonnull;
13 | import javax.annotation.ParametersAreNonnullByDefault;
14 | import java.util.Arrays;
15 | import java.util.Collection;
16 | import java.util.Collections;
17 | import java.util.HashSet;
18 | import java.util.Set;
19 | import java.util.stream.Collectors;
20 | import java.util.stream.Stream;
21 |
22 | /**
23 | * A wrapper for efficient serialization of a {@link Permission}
24 | * when stored as a part of an {@link AbstractRole}.
25 | */
26 | @ParametersAreNonnullByDefault
27 | public final class PermissionWrapper implements Comparable {
28 | // should've been final but needs to be setup when the
29 | // object is deserialized from the XML config
30 | private transient Permission permission;
31 | private final String id;
32 |
33 | @Restricted(NoExternalUse.class)
34 | public static final Set DANGEROUS_PERMISSIONS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
35 | Jenkins.RUN_SCRIPTS,
36 | PluginManager.CONFIGURE_UPDATECENTER,
37 | PluginManager.UPLOAD_PLUGINS
38 | )));
39 |
40 | /**
41 | * Constructor.
42 | *
43 | * @param id the id of the permission this {@link PermissionWrapper} contains.
44 | */
45 | @DataBoundConstructor
46 | public PermissionWrapper(String id) {
47 | this.id = id;
48 | permission = PermissionFinder.findPermission(id);
49 | checkPermission();
50 | }
51 |
52 | public String getId() {
53 | return String.format("%s/%s", permission.group.getId(), permission.name);
54 | }
55 |
56 | /**
57 | * Used to setup the permission when deserialized
58 | *
59 | * @return the {@link PermissionWrapper}
60 | */
61 | @Nonnull
62 | @SuppressWarnings("unused")
63 | private Object readResolve() {
64 | permission = PermissionFinder.findPermission(id);
65 | checkPermission();
66 | return this;
67 | }
68 |
69 | /**
70 | * Get the permission corresponding to this {@link PermissionWrapper}
71 | *
72 | * @return the permission corresponding to this {@link PermissionWrapper}
73 | */
74 | @Nonnull
75 | public Permission getPermission() {
76 | return permission;
77 | }
78 |
79 |
80 | @Override
81 | public boolean equals(Object o) {
82 | if (this == o) return true;
83 | if (o == null || getClass() != o.getClass()) return false;
84 | PermissionWrapper that = (PermissionWrapper) o;
85 | return id.equals(that.id);
86 | }
87 |
88 | @Override
89 | public int hashCode() {
90 | return id.hashCode();
91 | }
92 |
93 | /**
94 | * Checks if the permission for this {@link PermissionWrapper} is valid.
95 | *
96 | * @throws IllegalArgumentException when the permission did not exist, was null or was dangerous.
97 | */
98 | private void checkPermission() {
99 | if (permission == null) {
100 | throw new IllegalArgumentException(Messages.PermissionWrapper_UnknownPermission(id));
101 | } else if (DANGEROUS_PERMISSIONS.contains(permission)) {
102 | throw new IllegalArgumentException(Messages.PermissionWrapper_NoDangerousPermissions());
103 | }
104 | }
105 |
106 | /**
107 | * Convenience method to wrap {@link Permission}s into {@link PermissionWrapper}s.
108 | *
109 | * @param permissions permissions to be wrapped up
110 | * @return a set containing a {@link PermissionWrapper} for each permission in {@code permissions}
111 | */
112 | @Nonnull
113 | public static Set wrapPermissions(Permission... permissions) {
114 | return _wrapPermissions(Arrays.stream(permissions));
115 | }
116 |
117 | /**
118 | * Convenience method to wrap {@link Permission}s into {@link PermissionWrapper}s.
119 | *
120 | * @param permissions permissions to be wrapped up
121 | * @return a set containing a {@link PermissionWrapper} for each permission in {@code permissions}
122 | */
123 | @Nonnull
124 | public static Set wrapPermissions(Collection permissions) {
125 | return _wrapPermissions(permissions.stream());
126 | }
127 |
128 | @Nonnull
129 | private static Set _wrapPermissions(Stream stream) {
130 | return stream
131 | .map(Permission::getId)
132 | .map(PermissionWrapper::new)
133 | .collect(Collectors.toSet());
134 | }
135 |
136 | @Override
137 | public int compareTo(@Nonnull PermissionWrapper other) {
138 | return Permission.ID_COMPARATOR.compare(this.permission, other.permission);
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/folderauth/FolderBasedAuthorizationStrategyTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth;
2 |
3 | import com.cloudbees.hudson.plugins.folder.Folder;
4 | import com.google.common.collect.ImmutableSet;
5 | import hudson.model.FreeStyleProject;
6 | import hudson.model.Item;
7 | import hudson.model.User;
8 | import hudson.security.ACL;
9 | import hudson.security.ACLContext;
10 | import hudson.security.Permission;
11 | import hudson.security.PermissionGroup;
12 | import io.jenkins.plugins.folderauth.roles.FolderRole;
13 | import io.jenkins.plugins.folderauth.roles.GlobalRole;
14 | import jenkins.model.Jenkins;
15 | import org.junit.Before;
16 | import org.junit.Rule;
17 | import org.junit.Test;
18 | import org.jvnet.hudson.test.JenkinsRule;
19 |
20 | import java.util.Collections;
21 | import java.util.HashSet;
22 |
23 | import static io.jenkins.plugins.folderauth.misc.PermissionWrapper.wrapPermissions;
24 | import static junit.framework.TestCase.assertFalse;
25 | import static junit.framework.TestCase.assertTrue;
26 |
27 | public class FolderBasedAuthorizationStrategyTest {
28 | @Rule
29 | public JenkinsRule jenkinsRule = new JenkinsRule();
30 |
31 | private Folder root;
32 | private Folder child1;
33 | private Folder child2;
34 | private Folder child3;
35 |
36 | private FreeStyleProject job1;
37 | private FreeStyleProject job2;
38 |
39 | private User admin;
40 | private User user1;
41 | private User user2;
42 |
43 | @Before
44 | public void setUp() throws Exception {
45 | Jenkins jenkins = jenkinsRule.jenkins;
46 | jenkins.setSecurityRealm(jenkinsRule.createDummySecurityRealm());
47 |
48 | FolderBasedAuthorizationStrategy strategy = new FolderBasedAuthorizationStrategy(Collections.emptySet(),
49 | Collections.emptySet(), Collections.emptySet());
50 | jenkins.setAuthorizationStrategy(strategy);
51 |
52 | final String adminRoleName = "adminRole";
53 | final String overallReadRoleName = "overallRead";
54 |
55 | FolderAuthorizationStrategyAPI.addGlobalRole(new GlobalRole(adminRoleName,
56 | wrapPermissions(FolderAuthorizationStrategyManagementLink.getSafePermissions(
57 | new HashSet<>(PermissionGroup.getAll())))));
58 |
59 | FolderAuthorizationStrategyAPI.assignSidToGlobalRole("admin", adminRoleName);
60 |
61 | FolderAuthorizationStrategyAPI.addGlobalRole(new GlobalRole(overallReadRoleName, wrapPermissions(Permission.READ)));
62 | FolderAuthorizationStrategyAPI.assignSidToGlobalRole("authenticated", overallReadRoleName);
63 |
64 | FolderAuthorizationStrategyAPI.addFolderRole(new FolderRole("folderRole1", wrapPermissions(Item.READ),
65 | ImmutableSet.of("root")));
66 | FolderAuthorizationStrategyAPI.assignSidToFolderRole("user1", "folderRole1");
67 | FolderAuthorizationStrategyAPI.assignSidToFolderRole("user2", "folderRole1");
68 |
69 | FolderAuthorizationStrategyAPI.addFolderRole(new FolderRole("folderRole2", wrapPermissions(Item.CONFIGURE, Item.DELETE),
70 | ImmutableSet.of("root/child1")));
71 | FolderAuthorizationStrategyAPI.assignSidToFolderRole("user2", "folderRole2");
72 |
73 | /*
74 | * Folder hierarchy for the test
75 | *
76 | * root
77 | * / \
78 | * child1 child2
79 | * / \
80 | * child3 job1
81 | * /
82 | * job2
83 | */
84 |
85 | root = jenkins.createProject(Folder.class, "root");
86 | child1 = root.createProject(Folder.class, "child1");
87 | child2 = root.createProject(Folder.class, "child2");
88 | child3 = child1.createProject(Folder.class, "child3");
89 |
90 | job1 = child2.createProject(FreeStyleProject.class, "job1");
91 | job2 = child3.createProject(FreeStyleProject.class, "job2");
92 |
93 | admin = User.getById("admin", true);
94 | user1 = User.getById("user1", true);
95 | user2 = User.getById("user2", true);
96 | }
97 |
98 | @Test
99 | public void permissionTest() {
100 | Jenkins jenkins = jenkinsRule.jenkins;
101 |
102 | try (ACLContext ignored = ACL.as(admin)) {
103 | assertTrue(jenkins.hasPermission(Jenkins.ADMINISTER));
104 | assertTrue(child3.hasPermission(Item.CONFIGURE));
105 | assertTrue(job1.hasPermission(Item.READ));
106 | assertTrue(job2.hasPermission(Item.CREATE));
107 | }
108 |
109 | try (ACLContext ignored = ACL.as(user1)) {
110 | assertTrue(jenkins.hasPermission(Permission.READ));
111 | assertTrue(root.hasPermission(Item.READ));
112 | assertTrue(job1.hasPermission(Item.READ));
113 | assertTrue(job2.hasPermission(Item.READ));
114 |
115 | assertFalse(job1.hasPermission(Item.CREATE));
116 | assertFalse(job1.hasPermission(Item.DELETE));
117 | assertFalse(job1.hasPermission(Item.CONFIGURE));
118 | assertFalse(job2.hasPermission(Item.CREATE));
119 | assertFalse(job2.hasPermission(Item.CONFIGURE));
120 | }
121 |
122 | try (ACLContext ignored = ACL.as(user2)) {
123 | assertTrue(jenkins.hasPermission(Permission.READ));
124 | assertTrue(child2.hasPermission(Item.READ));
125 | assertTrue(child1.hasPermission(Item.READ));
126 | assertTrue(job2.hasPermission(Item.CONFIGURE));
127 | assertFalse(job1.hasPermission(Item.CONFIGURE));
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/docs/usage.md:
--------------------------------------------------------------------------------
1 | # Using the plugin
2 |
3 | This plugin allows administrator users to allow or restrict users permissions.
4 |
5 | This plugin follows a 'role'-based model in where, depending on the context,
6 | a 'role' is applicable on multiple objects, can be assigned to multiple
7 | users and can grant a number of permissions. All users assigned to the role
8 | get all the permissions granted by that role for every object the role is
9 | valid on.
10 |
11 | This plugin provides multiple types of roles for managing permissions which
12 | can all be used simultaneously to provide a flexible way to manage permissions:
13 |
14 | * **Global Roles**: these roles give users permissions which are applicable
15 | everywhere in Jenkins.
16 | * **Folder Roles**: these roles allow managing permissions for individual
17 | projects organized in 'folders' from Cloudbees'
18 | [Folders plugin](https://plugins.jenkins.io/cloudbees-folder).
19 | * **Agent Roles**: these roles allow managing permissions for agents connected
20 | to Jenkins.
21 |
22 | ## Setting up
23 |
24 | To use this plugin, you need to set it as the authorization strategy from the
25 | 'Configure Global Security' page. To do that:
26 |
27 | 1. Log in as a user with administrator permissions.
28 | 2. Go to the 'Manage Jenkins' page which should visible on the sidebar from
29 | Jenkins' home page.
30 | 3. Go to the 'Configure Global Security' page.
31 | 4. Ensure that security for your Jenkins instance is enabled.
32 | 5. Choose 'Folder Authorization Strategy' as the authorization and save the
33 | configuration.
34 |
35 | Folder based authorization is now active 🎉. Now go back to the 'Manage
36 | Jenkins' page and scroll down. You should see a linkfor the 'Folder
37 | Authorization Strategy'. Click on the link and you can now
38 | start configuring permissions for users.
39 |
40 | ### Adding Roles
41 |
42 | The process to add any type of role is similar. Here we show how to add a
43 | 'Folder Role'.
44 |
45 | 
46 |
47 | 1. Choose a name for the role. The name of the role uniquely identifies it and
48 | is useful for invoking the REST API methods.
49 | 2. Choose the permissions that would be granted through this role. You can
50 | select multiple permissions by holding down the control key on your keyboard.
51 | 3. Choose the folders on which this role will be applicable.
52 | 4. Click the 'Add role' button and you're done!
53 |
54 | ### Assigning Roles to users
55 |
56 | It is really easy to assign roles to users and groups. Just follow the steps
57 | below.
58 |
59 | 
60 |
61 | 1. Find the role, type the user's ID in the text field.
62 | 2. Click the submit button.
63 | 3. The role was assigned to the user!
64 |
65 | **Note**: Apart from the user IDs provided by your security realm, Jenkins
66 | provides two groups, `authenticated` and `anonymous`, which as their names
67 | suggest, apply to authenticated and non-authenticated users, respectively.
68 | Therefore, if you assign a role to the `authenticated` sid, permissions would
69 | be granted to all logged-in users.
70 |
71 | ### Deleting a Role
72 |
73 | Just click the big red ❌ on the top right of the role you want to delete and
74 | you're done.
75 |
76 | ## Inheritance of Folder Roles
77 |
78 | When you add a folder role that is applicable on a *parent* folder, the
79 | permissions granted by that role are applicable to all of its children.
80 | Therefore, all permissions granted to a folder for a user are applicable to
81 | all folders and jobs nested under it.
82 |
83 | ## Using Agent Roles
84 |
85 | Agent roles can be used to configure users' permissions for agents connected
86 | to Jenkins. Multiple agents can be selected when creating an agent role, just
87 | like with Folder roles. All users assigned to this role will get all
88 | permissions granted by an agent role for all agents on which this role is
89 | applicable.
90 |
91 | Unlike Folder Roles, there is no support for inheritance of Agent Roles
92 | because Jenkins does not support nesting of agents.
93 |
94 | ## Jenkins Configuration-as-Code Support
95 |
96 | This plugin supports configuration as code for hands-free setup of your
97 | Jenkins instance. The easiest way to get a configuration for your uses-case is
98 | to configure the plugin through the Web UI and then export the configuration
99 | as YAML and store it for later use.
100 |
101 | You can write the configuration manually too. As an example, a YAML
102 | configuration for this plugin typically looks like this:
103 |
104 | ```yaml
105 | jenkins:
106 | authorizationStrategy:
107 | folderBased:
108 | agentRoles:
109 | - agents:
110 | - "agent1"
111 | name: "agentRole1"
112 | permissions:
113 | - id: "Agent/Configure"
114 | - id: "Agent/Disconnect"
115 | sids:
116 | - "user1"
117 | folderRoles:
118 | - folders:
119 | - "root"
120 | name: "viewRoot"
121 | permissions:
122 | - id: "Job/Read"
123 | sids:
124 | - "user1"
125 | globalRoles:
126 | - name: "admin"
127 | permissions:
128 | - id: "Overall/Administer"
129 | sids:
130 | - "admin"
131 | - name: "read"
132 | permissions:
133 | - id: "Overall/Read"
134 | sids:
135 | - "user1"
136 | ```
137 |
138 | The configuration YAML also supports permission IDs which are used internally by Jenkins,
139 | for example, `hudson.model.Hudson.Administer`
140 |
141 | **Note**: You need to have [Configuration-as-Code](https://plugins.jenkins.io/configuration-as-code)
142 | plugin ≥ [1.24](https://github.com/jenkinsci/configuration-as-code-plugin/releases/tag/configuration-as-code-1.24)
143 | installed for using Jenkins Configuration-as-Code with this plugin. The CasC
144 | plugin will **not** be installed when you install this plugin.
145 |
--------------------------------------------------------------------------------
/docs/rest-api.adoc:
--------------------------------------------------------------------------------
1 | = REST API methods
2 | :toc:
3 |
4 | The plugin provides REST APIs for modifying the roles. All of these methods
5 | require the user invoking them to have the `Jenkins.ADMINISTER` permission.
6 |
7 | == API methods for adding a role
8 |
9 | === `addGlobalRole`
10 |
11 | Adds a global role with no sids assigned to it. Requires POST to `${JENKINS_URL}/folder-auth/addGlobalRole`.
12 | The request body should specify the role to be added as a JSON object. For
13 | example, to add a role with name `foo` and providing the `Item | Delete` and the `Item | Configure` permissions, the request body should look like this:
14 |
15 | [source,json]
16 | ----
17 | {
18 | "name": "foo",
19 | "permissions": [
20 | "hudson.model.Item.Delete",
21 | "hudson.model.Item.Configure"
22 | ]
23 | }
24 | ----
25 |
26 | For example, you can add the role using `curl` like this:
27 |
28 | [source,bash]
29 | ----
30 | curl -X POST -H "Content-Type: application/json" -d \
31 | '{
32 | "name": "foo",
33 | "permissions": [
34 | "hudson.model.Item.Delete",
35 | "hudson.model.Item.Configure"
36 | ]
37 | }' localhost:8080/folder-auth/addGlobalRole
38 | ----
39 |
40 | === `addFolderRole`
41 |
42 | Adds a folder role with no sids assigned to it. Requires POST to `${JENKINS_URL}/folder-auth/addFolderRole`.
43 | The request body should specify the role to be added as a JSON object. For
44 | example, to add a role with name `foo` and providing the`Item | Delete` and
45 | the `Item | Configure` permissions on folders `bar`and `foo/baz`, the
46 | request body should look like this:
47 |
48 | [source,json]
49 | ----
50 | {
51 | "name": "foo",
52 | "permissions": [
53 | "hudson.model.Item.Delete",
54 | "hudson.model.Item.Configure"
55 | ],
56 | "folderNames": [
57 | "foo/baz",
58 | "bar"
59 | ]
60 | }
61 | ----
62 |
63 | === `addAgentRole`
64 |
65 | Adds an agent role with no sids assigned to it. Requires POST to `${JENKINS_URL}/folder-auth/addAgentRole`.
66 | The request body should specify the role to be added as a JSON object. For
67 | example, to add a role with the equal to `foo` and providing the
68 | `Agent | Configure` permissions on agents `bar` and `baz`, the request body
69 | should look like this:
70 |
71 | [source,json]
72 | ----
73 | {
74 | "name": "foo",
75 | "permissions": [
76 | "hudson.model.Computer.Configure"
77 | ],
78 | "agentNames": [
79 | "bar",
80 | "baz"
81 | ]
82 | }
83 | ----
84 |
85 | == API methods for assigning a sid to a role
86 |
87 | === `assignSidToGlobalRole`
88 |
89 | Assigns a sid to the role identified by its name. Requires POST to
90 | `${JENKINS_URL}/folder-auth/assignSidToGlobalRole`. The following query
91 | parameters are required:
92 |
93 | * `roleName`: The sid will be assigned to the global role with the name equal
94 | to this parameter.
95 | * `sid`: The sid to be assigned to the role with the name equal to the value of
96 | `roleName`.
97 |
98 | Using `curl`, for example, a sid "foo" can be assigned to the role "bar"
99 |
100 | [source,bash]
101 | ----
102 | curl -X POST -d 'roleName=bar&sid=foo' \
103 | http://localhost:8080/folder-auth/assignSidToGlobalRole
104 | ----
105 |
106 | === `assignSidToFolderRole`
107 |
108 | Assigns a sid to the role identified by its name. Requires POST to
109 | `${JENKINS_URL}/folder-auth/assignSidToFolderRole`. The following query
110 | parameters are required:
111 |
112 | * `roleName`: The sid will be assigned to the folder role with the name equal
113 | to this parameter.
114 | * `sid`: The sid to be assigned to the role with the name equal to the value of
115 | `roleName`.
116 |
117 | Using `curl`, for example, a sid "foo" can be assigned to the role "bar"
118 |
119 | [source,bash]
120 | ----
121 | curl -X POST -d 'roleName=bar&sid=foo' \
122 | http://localhost:8080/folder-auth/assignSidToFolderRole
123 | ----
124 |
125 | === `assignSidToAgentRole`
126 |
127 | Assigns a sid to the role identified by its name. Requires POST to
128 | `${JENKINS_URL}/folder-auth/assignSidToAgentRole`. The following query
129 | parameters are required:
130 |
131 | * `roleName`: The sid will be assigned to the agent role with the name equal
132 | to this parameter.
133 | * `sid`: The sid to be assigned to the role with the name equal to the value of
134 | `roleName`.
135 |
136 | Using `curl`, for example, a sid "foo" can be assigned to the role "bar"
137 |
138 | [source,bash]
139 | ----
140 | curl -X POST -d 'roleName=bar&sid=foo' \
141 | http://localhost:8080/folder-auth/assignSidToAgentRole
142 | ----
143 |
144 | == API methods for deleting a role
145 |
146 | === `deleteGlobalRole`
147 |
148 | Deletes a global role identified by its name. Requires POST to
149 | `${JENKINS_URL}/folder-auth/deleteGlobalRole`. The query parameter
150 | `roleName` is required.
151 |
152 | Using `curl`, for example, a role with name "foo" can be deleted
153 |
154 | [source,bash]
155 | ----
156 | curl -X POST -d 'roleName=foo' http://localhost:8080/folder-auth/deleteGlobalRole
157 | ----
158 |
159 | === `deleteFolderRole`
160 |
161 | Deletes a folder role identified by its name. Requires POST to
162 | `${JENKINS_URL}/folder-auth/deleteGlobalRole`. The parameter
163 | `roleName` is required.
164 |
165 | [source,bash]
166 | ----
167 | curl -X POST -d 'roleName=foo' http://localhost:8080/folder-auth/deleteFolderRole
168 | ----
169 |
170 | === `deleteAgentRole`
171 |
172 | Deletes an agent role identified by its name. Requires POST to
173 | `${JENKINS_URL}/folder-auth/deleteGlobalRole`. The parameter
174 | `roleName` is required.
175 |
176 | [source,bash]
177 | ----
178 | curl -X POST -d 'roleName=foo' http://localhost:8080/folder-auth/deleteAgentRole
179 | ----
180 |
181 | == Logging in to Jenkins
182 |
183 | When using cURL to invoke the API, you need to login as a user with the
184 | administrator permissions. See the example below for viewing the home page:
185 |
186 | [source,bash]
187 | ----
188 | curl -X GET -u $USERNAME:$TOKEN http://localhost:8080/
189 | ----
190 |
191 | The API token can be obtained by clicking on your logged in user on the top-right
192 | of your Jenkins Home page and then clicking the 'Configure' button in the side bar.
193 | For more information about authentication, please see https://wiki.jenkins.io/display/JENKINS/Remote+access+API
194 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/folderauth/FolderAuthorizationStrategyAPITest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth;
2 |
3 | import hudson.model.Computer;
4 | import hudson.model.Item;
5 | import hudson.security.AuthorizationStrategy;
6 | import hudson.security.Permission;
7 | import io.jenkins.plugins.folderauth.roles.AgentRole;
8 | import io.jenkins.plugins.folderauth.roles.FolderRole;
9 | import io.jenkins.plugins.folderauth.roles.GlobalRole;
10 | import jenkins.model.Jenkins;
11 | import net.sf.json.JSONObject;
12 | import org.junit.Before;
13 | import org.junit.Rule;
14 | import org.junit.Test;
15 | import org.jvnet.hudson.test.JenkinsRule;
16 |
17 | import static io.jenkins.plugins.folderauth.misc.PermissionWrapper.wrapPermissions;
18 | import static java.util.Collections.emptySet;
19 | import static java.util.Collections.singleton;
20 | import static org.junit.Assert.assertEquals;
21 | import static org.junit.Assert.assertFalse;
22 | import static org.junit.Assert.assertNotSame;
23 | import static org.junit.Assert.assertTrue;
24 |
25 |
26 | public class FolderAuthorizationStrategyAPITest {
27 |
28 | @Rule
29 | public JenkinsRule j = new JenkinsRule();
30 |
31 | @Before
32 | public void setUp() {
33 | j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
34 | FolderBasedAuthorizationStrategy strategy = new FolderBasedAuthorizationStrategy.DescriptorImpl()
35 | .newInstance(null, new JSONObject(true));
36 | // should only create the admin global role
37 | assertEquals(1, strategy.getGlobalRoles().size());
38 | assertEquals(0, strategy.getFolderRoles().size());
39 | assertEquals(0, strategy.getAgentRoles().size());
40 |
41 | j.jenkins.setAuthorizationStrategy(strategy);
42 | }
43 |
44 | @Test
45 | public void addGlobalRole() {
46 | GlobalRole readRole = new GlobalRole("readEverything", wrapPermissions(Jenkins.READ), singleton("user1"));
47 | FolderAuthorizationStrategyAPI.addGlobalRole(readRole);
48 | AuthorizationStrategy a = j.jenkins.getAuthorizationStrategy();
49 | assertTrue(a instanceof FolderBasedAuthorizationStrategy);
50 | FolderBasedAuthorizationStrategy strategy = (FolderBasedAuthorizationStrategy) a;
51 | assertTrue(strategy.getGlobalRoles().contains(readRole));
52 | }
53 |
54 | @Test
55 | public void addFolderRole() {
56 | FolderRole role = new FolderRole("readEverything", wrapPermissions(Jenkins.READ),
57 | singleton("folder1"), singleton("user1"));
58 | FolderAuthorizationStrategyAPI.addFolderRole(role);
59 | AuthorizationStrategy a = j.jenkins.getAuthorizationStrategy();
60 | assertTrue(a instanceof FolderBasedAuthorizationStrategy);
61 | FolderBasedAuthorizationStrategy strategy = (FolderBasedAuthorizationStrategy) a;
62 | assertTrue(strategy.getFolderRoles().contains(role));
63 | }
64 |
65 | @Test
66 | public void addAgentRole() {
67 | AgentRole role = new AgentRole("readEverything", wrapPermissions(Jenkins.READ),
68 | singleton("agent1"), singleton("user1"));
69 | FolderAuthorizationStrategyAPI.addAgentRole(role);
70 | AuthorizationStrategy a = j.jenkins.getAuthorizationStrategy();
71 | assertTrue(a instanceof FolderBasedAuthorizationStrategy);
72 | FolderBasedAuthorizationStrategy strategy = (FolderBasedAuthorizationStrategy) a;
73 | assertTrue(strategy.getAgentRoles().contains(role));
74 | }
75 |
76 | @Test
77 | public void assignSidToGlobalRole() {
78 | AuthorizationStrategy a = j.jenkins.getAuthorizationStrategy();
79 | assertTrue(a instanceof FolderBasedAuthorizationStrategy);
80 | FolderBasedAuthorizationStrategy oldStrategy = (FolderBasedAuthorizationStrategy) a;
81 | String adminUserSid = "adminUserSid";
82 | oldStrategy.getGlobalRoles().forEach(role -> assertFalse(role.getSids().contains(adminUserSid)));
83 | String adminRoleName = "admin";
84 | FolderAuthorizationStrategyAPI.assignSidToGlobalRole(adminUserSid, adminRoleName);
85 |
86 | // a new authorization strategy should have been set
87 | AuthorizationStrategy b = j.jenkins.getAuthorizationStrategy();
88 | assertTrue(b instanceof FolderBasedAuthorizationStrategy);
89 | assertNotSame("A new instance of FolderBasedAuthorizationStrategy should have been set.", a, b);
90 | FolderBasedAuthorizationStrategy newStrategy = (FolderBasedAuthorizationStrategy) b;
91 | GlobalRole role = newStrategy.getGlobalRoles().stream().filter(r -> r.getName().equals(adminRoleName))
92 | .findAny().orElseThrow(() -> new RuntimeException("The admin role should exist"));
93 | assertTrue(role.getSids().contains(adminUserSid));
94 | }
95 |
96 | @Test
97 | public void assignSidToFolderRole() {
98 | String sid = "user1";
99 | FolderRole role = new FolderRole("foo", wrapPermissions(Item.READ), singleton("folderFoo"));
100 | assertEquals(0, role.getSids().size());
101 | FolderAuthorizationStrategyAPI.addFolderRole(role);
102 | FolderAuthorizationStrategyAPI.assignSidToFolderRole(sid, "foo");
103 |
104 |
105 | AuthorizationStrategy a = j.jenkins.getAuthorizationStrategy();
106 | assertTrue(a instanceof FolderBasedAuthorizationStrategy);
107 | FolderBasedAuthorizationStrategy strategy = (FolderBasedAuthorizationStrategy) a;
108 | FolderRole updatedRole = strategy.getFolderRoles().stream().filter(r -> r.getName().equals("foo"))
109 | .findAny().orElseThrow(() -> new RuntimeException("The created role should exist"));
110 | assertTrue(updatedRole.getSids().contains(sid));
111 | }
112 |
113 | @Test
114 | public void assignSidToAgentRole() {
115 | String sid = "user1";
116 | AgentRole role = new AgentRole("bar", wrapPermissions(Item.READ), singleton("agentBar"));
117 | assertEquals(0, role.getSids().size());
118 | FolderAuthorizationStrategyAPI.addAgentRole(role);
119 | FolderAuthorizationStrategyAPI.assignSidToAgentRole(sid, "bar");
120 |
121 | AuthorizationStrategy a = j.jenkins.getAuthorizationStrategy();
122 | assertTrue(a instanceof FolderBasedAuthorizationStrategy);
123 | FolderBasedAuthorizationStrategy strategy = (FolderBasedAuthorizationStrategy) a;
124 | AgentRole updatedRole = strategy.getAgentRoles().stream().filter(r -> r.getName().equals("bar"))
125 | .findAny().orElseThrow(() -> new RuntimeException("The created role should exist"));
126 | assertTrue(updatedRole.getSids().contains(sid));
127 | }
128 |
129 | @Test
130 | public void removeSidFromGlobalRole() {
131 | AuthorizationStrategy a = j.jenkins.getAuthorizationStrategy();
132 | assertTrue(a instanceof FolderBasedAuthorizationStrategy);
133 | final String adminRoleName = "admin";
134 | FolderAuthorizationStrategyAPI.assignSidToGlobalRole("user1", adminRoleName);
135 | FolderAuthorizationStrategyAPI.removeSidFromGlobalRole("user1", adminRoleName);
136 |
137 | // a new authorization strategy should have been set
138 | AuthorizationStrategy b = j.jenkins.getAuthorizationStrategy();
139 | assertTrue(b instanceof FolderBasedAuthorizationStrategy);
140 | assertNotSame("A new instance of FolderBasedAuthorizationStrategy should have been set.", a, b);
141 | FolderBasedAuthorizationStrategy newStrategy = (FolderBasedAuthorizationStrategy) b;
142 | GlobalRole role = newStrategy.getGlobalRoles().stream().filter(r -> r.getName().equals(adminRoleName))
143 | .findAny().orElseThrow(() -> new RuntimeException("The admin role should exist"));
144 | assertFalse(role.getSids().contains("user1"));
145 | }
146 |
147 | @Test
148 | public void removeSidFromFolderRole() {
149 | String sid = "user1";
150 | FolderRole role = new FolderRole("foo", wrapPermissions(Item.READ), singleton("folderFoo"));
151 | assertEquals(0, role.getSids().size());
152 | FolderAuthorizationStrategyAPI.addFolderRole(role);
153 | FolderAuthorizationStrategyAPI.assignSidToFolderRole(sid, "foo");
154 | FolderAuthorizationStrategyAPI.removeSidFromFolderRole(sid, "foo");
155 |
156 | AuthorizationStrategy a = j.jenkins.getAuthorizationStrategy();
157 | assertTrue(a instanceof FolderBasedAuthorizationStrategy);
158 | FolderBasedAuthorizationStrategy strategy = (FolderBasedAuthorizationStrategy) a;
159 | FolderRole updatedRole = strategy.getFolderRoles().stream().filter(r -> r.getName().equals("foo"))
160 | .findAny().orElseThrow(() -> new RuntimeException("The created role should exist"));
161 | assertFalse(updatedRole.getSids().contains(sid));
162 | }
163 |
164 | @Test
165 | public void removeSidFromAgentRole() {
166 | String sid = "user1";
167 | AgentRole role = new AgentRole("bar", wrapPermissions(Item.READ), singleton("agentBar"));
168 | assertEquals(0, role.getSids().size());
169 | FolderAuthorizationStrategyAPI.addAgentRole(role);
170 | FolderAuthorizationStrategyAPI.assignSidToAgentRole(sid, "bar");
171 | FolderAuthorizationStrategyAPI.removeSidFromAgentRole(sid, "bar");
172 |
173 | AuthorizationStrategy a = j.jenkins.getAuthorizationStrategy();
174 | assertTrue(a instanceof FolderBasedAuthorizationStrategy);
175 | FolderBasedAuthorizationStrategy strategy = (FolderBasedAuthorizationStrategy) a;
176 | AgentRole updatedRole = strategy.getAgentRoles().stream().filter(r -> r.getName().equals("bar"))
177 | .findAny().orElseThrow(() -> new RuntimeException("The created role should exist"));
178 | assertFalse(updatedRole.getSids().contains(sid));
179 | }
180 |
181 | @Test(expected = IllegalArgumentException.class)
182 | public void shouldNotAllowDuplicateNamesInGlobalRoles() {
183 | // the "admin" role should already exist
184 | FolderAuthorizationStrategyAPI.addGlobalRole(new GlobalRole("admin", wrapPermissions(Permission.READ),
185 | emptySet()));
186 | }
187 |
188 | @Test(expected = IllegalArgumentException.class)
189 | public void shouldNotAllowDuplicateNamesInFolderRoles() {
190 | FolderAuthorizationStrategyAPI.addFolderRole(new FolderRole("baz", wrapPermissions(Item.READ),
191 | singleton("folder42")));
192 | // different permissions shouldn't matter
193 | FolderAuthorizationStrategyAPI.addFolderRole(new FolderRole("baz", wrapPermissions(Item.CONFIGURE),
194 | singleton("folder42")));
195 | }
196 |
197 | @Test(expected = IllegalArgumentException.class)
198 | public void shouldNotAllowDuplicateNamesInAgentRoles() {
199 | FolderAuthorizationStrategyAPI.addAgentRole(new AgentRole("baz", wrapPermissions(Computer.DELETE),
200 | singleton("agent42")));
201 | // different agent names shouldn't matter
202 | FolderAuthorizationStrategyAPI.addAgentRole(new AgentRole("baz", wrapPermissions(Computer.CONFIGURE),
203 | singleton("agent43")));
204 | }
205 |
206 | @Test(expected = IllegalArgumentException.class)
207 | public void shouldNotAllowBlankSidInGlobalRoles() {
208 | FolderAuthorizationStrategyAPI.assignSidToGlobalRole("", "admin");
209 | }
210 |
211 | @Test(expected = IllegalArgumentException.class)
212 | public void shouldNotAllowBlankSidInFolderRoles() {
213 | FolderAuthorizationStrategyAPI.addFolderRole(new FolderRole("qwerty", wrapPermissions(Item.EXTENDED_READ),
214 | singleton("sampleFolder")));
215 | FolderAuthorizationStrategyAPI.assignSidToFolderRole(" \t", "qwerty");
216 | }
217 |
218 | @Test(expected = IllegalArgumentException.class)
219 | public void shouldNotAllowBlankSidInAgentRoles() {
220 | FolderAuthorizationStrategyAPI.addFolderRole(new FolderRole("foo", wrapPermissions(Item.EXTENDED_READ),
221 | singleton("sampleAgent")));
222 | FolderAuthorizationStrategyAPI.assignSidToFolderRole("\t\t \t", "foo");
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/folderauth/FolderBasedAuthorizationStrategy.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth;
2 |
3 | import com.google.common.cache.Cache;
4 | import com.google.common.cache.CacheBuilder;
5 | import hudson.Extension;
6 | import hudson.model.AbstractItem;
7 | import hudson.model.Computer;
8 | import hudson.model.Descriptor;
9 | import hudson.model.Job;
10 | import hudson.security.ACL;
11 | import hudson.security.AuthorizationStrategy;
12 | import hudson.security.Permission;
13 | import hudson.security.PermissionGroup;
14 | import hudson.security.SidACL;
15 | import io.jenkins.plugins.folderauth.acls.GenericAclImpl;
16 | import io.jenkins.plugins.folderauth.acls.GlobalAclImpl;
17 | import io.jenkins.plugins.folderauth.misc.PermissionWrapper;
18 | import io.jenkins.plugins.folderauth.roles.AbstractRole;
19 | import io.jenkins.plugins.folderauth.roles.AgentRole;
20 | import io.jenkins.plugins.folderauth.roles.FolderRole;
21 | import io.jenkins.plugins.folderauth.roles.GlobalRole;
22 | import jenkins.model.Jenkins;
23 | import net.sf.json.JSONObject;
24 | import org.acegisecurity.acls.sid.PrincipalSid;
25 | import org.kohsuke.stapler.DataBoundConstructor;
26 | import org.kohsuke.stapler.StaplerRequest;
27 |
28 | import javax.annotation.Nonnull;
29 | import javax.annotation.Nullable;
30 | import javax.annotation.ParametersAreNonnullByDefault;
31 | import java.util.Collection;
32 | import java.util.Collections;
33 | import java.util.HashSet;
34 | import java.util.Set;
35 | import java.util.concurrent.ConcurrentHashMap;
36 | import java.util.concurrent.TimeUnit;
37 | import java.util.stream.Collectors;
38 |
39 | /**
40 | * An {@link AuthorizationStrategy} that controls access to {@link com.cloudbees.hudson.plugins.folder.AbstractFolder}s
41 | * through {@link FolderRole}s, to {@link Computer}s through {@link AgentRole}s. Also provides global permissions
42 | * through {@link GlobalRole}s.
43 | *
44 | * All objects of this class are immutable. To modify the data for this strategy,
45 | * please use the {@link FolderAuthorizationStrategyAPI}.
46 | *
47 | * @see FolderAuthorizationStrategyAPI for modifying the roles
48 | * @see FolderAuthorizationStrategyManagementLink for REST API methods
49 | */
50 | @ParametersAreNonnullByDefault
51 | public class FolderBasedAuthorizationStrategy extends AuthorizationStrategy {
52 | private static final String ADMIN_ROLE_NAME = "admin";
53 | private static final String FOLDER_SEPARATOR = "/";
54 |
55 | private final Set agentRoles;
56 | private final Set globalRoles;
57 | private final Set folderRoles;
58 |
59 | /**
60 | * An {@link ACL} that works only on {@link #globalRoles}.
61 | *
62 | * All other {@link ACL} should inherit from this {@link ACL}.
63 | */
64 | private transient GlobalAclImpl globalAcl;
65 | /**
66 | * Maps full name of jobs to their respective {@link ACL}s. The {@link ACL}s here do not
67 | * get inheritance from their parents.
68 | */
69 | private transient ConcurrentHashMap jobAcls;
70 | /**
71 | * Maps full name of the Agents to their respective {@link ACL}s. Inheritance is not needed here
72 | * because Agents are not nestable.
73 | */
74 | private transient ConcurrentHashMap agentAcls;
75 | /**
76 | * Contains the ACLs for projects that do not need any further inheritance.
77 | *
78 | * Invalidate this cache whenever folder roles are updated.
79 | */
80 | private transient Cache jobAclCache;
81 |
82 | @DataBoundConstructor
83 | public FolderBasedAuthorizationStrategy(Set globalRoles, Set folderRoles,
84 | Set agentRoles) {
85 | this.agentRoles = new HashSet<>(agentRoles);
86 | this.globalRoles = new HashSet<>(globalRoles);
87 | this.folderRoles = new HashSet<>(folderRoles);
88 |
89 | // the sets above should NOT be modified. They are not Collections.unmodifiableSet()
90 | // because that complicates the serialized XML and add unnecessary nesting.
91 |
92 | init();
93 | }
94 |
95 | /**
96 | * Clears and recalculates {@code jobAcls}.
97 | */
98 | private void updateJobAcls() {
99 | jobAcls.clear();
100 |
101 | for (FolderRole role : folderRoles) {
102 | updateAclForFolderRole(role);
103 | }
104 | }
105 |
106 | private synchronized void updateAgentAcls() {
107 | agentAcls.clear();
108 |
109 | for (AgentRole role : agentRoles) {
110 | updateAclForAgentRole(role);
111 | }
112 | }
113 |
114 | /**
115 | * {@inheritDoc}
116 | *
117 | * @return an {@link ACL} formed using just globalRoles
118 | */
119 | @Nonnull
120 | @Override
121 | public GlobalAclImpl getRootACL() {
122 | return globalAcl;
123 | }
124 |
125 | /**
126 | * Used to initialize transient fields when loaded from disk
127 | *
128 | * @return {@code this}
129 | */
130 | @Nonnull
131 | @SuppressWarnings("unused")
132 | private FolderBasedAuthorizationStrategy readResolve() {
133 | init();
134 | return this;
135 | }
136 |
137 | /**
138 | * Gets the {@link ACL} for a {@link Job}
139 | *
140 | * @return the {@link ACL} for the {@link Job}
141 | */
142 | @Nonnull
143 | @Override
144 | public SidACL getACL(Job, ?> project) {
145 | return getACL((AbstractItem) project);
146 | }
147 |
148 | /**
149 | * {@inheritDoc}
150 | */
151 | @Nonnull
152 | @Override
153 | public SidACL getACL(AbstractItem item) {
154 | String fullName = item.getFullName();
155 | SidACL acl = jobAclCache.getIfPresent(fullName);
156 |
157 | if (acl != null) {
158 | return acl;
159 | }
160 |
161 | String[] splits = fullName.split(FOLDER_SEPARATOR);
162 | StringBuilder sb = new StringBuilder(fullName.length());
163 | acl = globalAcl;
164 |
165 | // Roles on a folder are applicable to all children
166 | for (String str : splits) {
167 | sb.append(str);
168 | SidACL newAcl = jobAcls.get(sb.toString());
169 | if (newAcl != null) {
170 | acl = acl.newInheritingACL(newAcl);
171 | }
172 | sb.append(FOLDER_SEPARATOR);
173 | }
174 |
175 | jobAclCache.put(fullName, acl);
176 | return acl;
177 | }
178 |
179 | /**
180 | * {@inheritDoc}
181 | */
182 | @Nonnull
183 | @Override
184 | public SidACL getACL(@Nonnull Computer computer) {
185 | String name = computer.getName();
186 | SidACL acl = agentAcls.get(name);
187 | if (acl == null) {
188 | return globalAcl;
189 | } else {
190 | // TODO: cache these ACLs
191 | return globalAcl.newInheritingACL(acl);
192 | }
193 | }
194 |
195 | /**
196 | * {@inheritDoc}
197 | */
198 | @Nonnull
199 | @Override
200 | public Collection getGroups() {
201 | Set groups = ConcurrentHashMap.newKeySet();
202 | agentRoles.stream().parallel().map(AbstractRole::getSids).forEach(groups::addAll);
203 | globalRoles.stream().parallel().map(AbstractRole::getSids).forEach(groups::addAll);
204 | folderRoles.stream().parallel().map(AbstractRole::getSids).forEach(groups::addAll);
205 | return Collections.unmodifiableCollection(groups);
206 | }
207 |
208 | /**
209 | * Returns the {@link GlobalRole}s on which this {@link AuthorizationStrategy} works.
210 | *
211 | * @return set of {@link GlobalRole}s on which this {@link AuthorizationStrategy} works.
212 | */
213 | @Nonnull
214 | public Set getGlobalRoles() {
215 | return Collections.unmodifiableSet(globalRoles);
216 | }
217 |
218 | /**
219 | * Returns the {@link AgentRole}s on which this {@link AuthorizationStrategy} works.
220 | *
221 | * @return set of {@link AgentRole}s on which this {@link AuthorizationStrategy} works.
222 | */
223 | @Nonnull
224 | public Set getAgentRoles() {
225 | return Collections.unmodifiableSet(agentRoles);
226 | }
227 |
228 | /**
229 | * Returns the {@link FolderRole}s on which this {@link AuthorizationStrategy} works.
230 | *
231 | * @return {@link FolderRole}s on which this {@link AuthorizationStrategy} works
232 | */
233 | @Nonnull
234 | public Set getFolderRoles() {
235 | return Collections.unmodifiableSet(folderRoles);
236 | }
237 |
238 | /**
239 | * Updates the ACL for the folder role
240 | *
241 | * Note: does not invalidate the cache
242 | *
243 | * Should be called when a folderRole has been updated.
244 | *
245 | * @param role the role to be updated
246 | */
247 | private void updateAclForFolderRole(FolderRole role) {
248 | for (String name : role.getFolderNames()) {
249 | updateGenericAcl(name, jobAcls, role);
250 | }
251 | }
252 |
253 | /**
254 | * Updates the ACL for the agent role
255 | *
256 | * Note: does not invalidate the cache
257 | *
258 | * Should be called when an agentRole has been updated.
259 | *
260 | * @param role the role to be updated
261 | */
262 | private void updateAclForAgentRole(AgentRole role) {
263 | for (String agent : role.getAgents()) {
264 | updateGenericAcl(agent, agentAcls, role);
265 | }
266 | }
267 |
268 | private void updateGenericAcl(String fullName, ConcurrentHashMap acls, AbstractRole role) {
269 | GenericAclImpl acl = acls.get(fullName);
270 | if (acl == null) {
271 | acl = new GenericAclImpl();
272 | }
273 | acl.assignPermissions(role.getSids(),
274 | role.getPermissionsUnsorted().stream().map(PermissionWrapper::getPermission).collect(Collectors.toSet()));
275 | acls.put(fullName, acl);
276 | }
277 |
278 | /**
279 | * Initializes the cache, generates ACLs and makes the {@link FolderBasedAuthorizationStrategy}
280 | * ready to work.
281 | */
282 | private void init() {
283 | jobAcls = new ConcurrentHashMap<>();
284 | agentAcls = new ConcurrentHashMap<>();
285 |
286 | jobAclCache = CacheBuilder.newBuilder()
287 | .expireAfterWrite(1, TimeUnit.HOURS)
288 | .maximumSize(2048)
289 | .build();
290 |
291 | globalAcl = new GlobalAclImpl(globalRoles);
292 | updateJobAcls();
293 | updateAgentAcls();
294 | }
295 |
296 | @Extension
297 | public static class DescriptorImpl extends Descriptor {
298 | @Nonnull
299 | @Override
300 | public String getDisplayName() {
301 | return Messages.FolderBasedAuthorizationStrategy_DisplayName();
302 | }
303 |
304 | @Nonnull
305 | @Override
306 | public FolderBasedAuthorizationStrategy newInstance(@Nullable StaplerRequest req, @Nonnull JSONObject formData) {
307 | AuthorizationStrategy strategy = Jenkins.get().getAuthorizationStrategy();
308 | if (strategy instanceof FolderBasedAuthorizationStrategy) {
309 | // this action was invoked from the 'Configure Global Security' page when the
310 | // old strategy was FolderBasedAuthorizationStrategy; return it back as formData would be empty
311 | return (FolderBasedAuthorizationStrategy) strategy;
312 | } else {
313 | // when this AuthorizationStrategy is selected for the first time, this makes the current
314 | // user admin (give all permissions) and prevents him/her from getting access denied.
315 | // The same thing happens in Role Strategy plugin. See RoleBasedStrategy.DESCRIPTOR.newInstance()
316 |
317 | HashSet groups = new HashSet<>(PermissionGroup.getAll());
318 | groups.remove(PermissionGroup.get(Permission.class));
319 | Set adminPermissions = PermissionWrapper.wrapPermissions(
320 | FolderAuthorizationStrategyManagementLink.getSafePermissions(groups));
321 |
322 | GlobalRole adminRole = new GlobalRole(ADMIN_ROLE_NAME, adminPermissions,
323 | Collections.singleton(new PrincipalSid(Jenkins.getAuthentication()).getPrincipal()));
324 |
325 | return new FolderBasedAuthorizationStrategy(Collections.singleton(adminRole), Collections.emptySet(),
326 | Collections.emptySet());
327 | }
328 | }
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/folderauth/FolderAuthorizationStrategyAPI.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth;
2 |
3 | import hudson.security.AuthorizationStrategy;
4 | import io.jenkins.plugins.folderauth.roles.AgentRole;
5 | import io.jenkins.plugins.folderauth.roles.FolderRole;
6 | import io.jenkins.plugins.folderauth.roles.GlobalRole;
7 | import jenkins.model.Jenkins;
8 | import org.apache.commons.lang.StringUtils;
9 |
10 | import javax.annotation.ParametersAreNonnullByDefault;
11 | import java.util.HashSet;
12 | import java.util.Optional;
13 | import java.util.Set;
14 | import java.util.function.Consumer;
15 | import java.util.function.Function;
16 |
17 | /**
18 | * Public-facing methods for modifying {@link FolderBasedAuthorizationStrategy}.
19 | *
20 | * These methods should only be called when {@link Jenkins#getAuthorizationStrategy()}} is
21 | * {@link FolderBasedAuthorizationStrategy}. This class does not provide REST API methods.
22 | *
23 | * @see FolderAuthorizationStrategyManagementLink for REST API methods.
24 | */
25 | @ParametersAreNonnullByDefault
26 | @SuppressWarnings("WeakerAccess")
27 | public class FolderAuthorizationStrategyAPI {
28 |
29 | private FolderAuthorizationStrategyAPI() {
30 | }
31 |
32 | /**
33 | * Checks the {@link AuthorizationStrategy} and runs the {@link Consumer} when it is an instance of
34 | * {@link FolderBasedAuthorizationStrategy}.
35 | *
36 | * All attempts to access the {@link FolderBasedAuthorizationStrategy} must go through this method
37 | * for thread-safety.
38 | *
39 | * @param runner a function that consumes the current {@link FolderBasedAuthorizationStrategy} and returns a non
40 | * null {@link FolderBasedAuthorizationStrategy} object. The object may be the same as the one
41 | * consumed if no modification was needed.
42 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is not
43 | * {@link FolderBasedAuthorizationStrategy}
44 | */
45 | private synchronized static void run(Function runner) {
46 | Jenkins jenkins = Jenkins.get();
47 | AuthorizationStrategy strategy = jenkins.getAuthorizationStrategy();
48 | if (strategy instanceof FolderBasedAuthorizationStrategy) {
49 | FolderBasedAuthorizationStrategy newStrategy = runner.apply((FolderBasedAuthorizationStrategy) strategy);
50 | jenkins.setAuthorizationStrategy(newStrategy);
51 | } else {
52 | throw new IllegalStateException("FolderBasedAuthorizationStrategy is not the" + " current authorization strategy");
53 | }
54 | }
55 |
56 | /**
57 | * Adds a {@link GlobalRole} to the {@link FolderBasedAuthorizationStrategy}.
58 | *
59 | * @param role the role to be added.
60 | * @throws IllegalArgumentException when a role with the given name already exists.
61 | */
62 | public static void addGlobalRole(GlobalRole role) {
63 | run(strategy -> {
64 | Set globalRoles = new HashSet<>(strategy.getGlobalRoles());
65 | String name = role.getName();
66 | Optional existing = globalRoles.stream().filter(r -> r.getName().equals(name)).findAny();
67 | if (existing.isPresent()) {
68 | throw new IllegalArgumentException("A global role with the name \"" + name + "\" already exists.");
69 | }
70 | globalRoles.add(role);
71 | return new FolderBasedAuthorizationStrategy(globalRoles, strategy.getFolderRoles(), strategy.getAgentRoles());
72 | });
73 | }
74 |
75 | /**
76 | * Adds a {@link FolderRole} to the {@link FolderBasedAuthorizationStrategy}.
77 | *
78 | * @param role the role to be added.
79 | * @throws IllegalArgumentException when a role with the given name already exists.
80 | */
81 | public static void addFolderRole(FolderRole role) {
82 | run(strategy -> {
83 | Set folderRoles = new HashSet<>(strategy.getFolderRoles());
84 | String name = role.getName();
85 | Optional existing = folderRoles.stream().filter(r -> r.getName().equals(name)).findAny();
86 | if (existing.isPresent()) {
87 | throw new IllegalArgumentException("A folder role with the name \"" + name + "\" already exists.");
88 | }
89 | folderRoles.add(role);
90 | return new FolderBasedAuthorizationStrategy(strategy.getGlobalRoles(), folderRoles, strategy.getAgentRoles());
91 | });
92 | }
93 |
94 | /**
95 | * Adds an {@link AgentRole} to the {@link FolderBasedAuthorizationStrategy}.
96 | *
97 | * @param role the role to be added.
98 | * @throws IllegalArgumentException when a role with the given name already exists.
99 | */
100 | public static void addAgentRole(AgentRole role) {
101 | run(strategy -> {
102 | Set agentRoles = new HashSet<>(strategy.getAgentRoles());
103 | String name = role.getName();
104 | Optional existing = agentRoles.stream().filter(r -> r.getName().equals(name)).findAny();
105 | if (existing.isPresent()) {
106 | throw new IllegalArgumentException("An agent role with the name \"" + name + "\" already exists.");
107 | }
108 | agentRoles.add(role);
109 | return new FolderBasedAuthorizationStrategy(strategy.getGlobalRoles(), strategy.getFolderRoles(), agentRoles);
110 | });
111 | }
112 |
113 | /**
114 | * Assigns the {@code sid} to the {@link GlobalRole} identified by {@code roleName}.
115 | *
116 | * @param sid this sid will be assigned to the global role with the name equal to {@code roleName}.
117 | * @param roleName the name of the global role
118 | * @throws IllegalArgumentException when no global role with name equal to {@code roleName} exists
119 | * @throws IllegalArgumentException when the {@code sid} is empty
120 | */
121 | public static void assignSidToGlobalRole(String sid, String roleName) {
122 | if (StringUtils.isBlank(sid)) {
123 | throw new IllegalArgumentException("Sid should not be blank.");
124 | }
125 |
126 | run(strategy -> {
127 | Set globalRoles = new HashSet<>(strategy.getGlobalRoles());
128 | GlobalRole role = globalRoles.stream().filter(r -> r.getName().equals(roleName)).findAny().orElseThrow(
129 | () -> new IllegalArgumentException("No global role with name = \"" + roleName + "\" exists"));
130 | HashSet newSids = new HashSet<>(role.getSids());
131 | newSids.add(sid);
132 | globalRoles.remove(role);
133 | globalRoles.add(new GlobalRole(role.getName(), role.getPermissionsUnsorted(), newSids));
134 | return new FolderBasedAuthorizationStrategy(globalRoles, strategy.getFolderRoles(), strategy.getAgentRoles());
135 | });
136 | }
137 |
138 | /**
139 | * Assigns the {@code sid} to the {@link AgentRole} identified by {@code roleName}.
140 | *
141 | * @param sid this sid will be assigned to the {@link AgentRole} with the name equal to {@code roleName}.
142 | * @param roleName the name of the agent role
143 | * @throws IllegalArgumentException when no agent role with name equal to {@code roleName} exists
144 | * @throws IllegalArgumentException when the {@code sid} is empty
145 | */
146 | public static void assignSidToAgentRole(String sid, String roleName) {
147 | if (StringUtils.isBlank(sid)) {
148 | throw new IllegalArgumentException("Sid should not be blank.");
149 | }
150 |
151 | run(strategy -> {
152 | Set agentRoles = new HashSet<>(strategy.getAgentRoles());
153 | AgentRole role = agentRoles.stream().filter(r -> r.getName().equals(roleName)).findAny().orElseThrow(
154 | () -> new IllegalArgumentException("No agent role with name = \"" + roleName + "\" exists"));
155 | HashSet newSids = new HashSet<>(role.getSids());
156 | newSids.add(sid);
157 | agentRoles.remove(role);
158 | agentRoles.add(new AgentRole(role.getName(), role.getPermissionsUnsorted(), role.getAgents(), newSids));
159 | return new FolderBasedAuthorizationStrategy(strategy.getGlobalRoles(), strategy.getFolderRoles(), agentRoles);
160 | });
161 | }
162 |
163 | /**
164 | * Assigns the {@code sid} to the {@link FolderRole} identified by {@code roleName}.
165 | *
166 | * @param sid this sid will be assigned to the {@link FolderRole} with the name equal to {@code roleName}.
167 | * @param roleName the name of the folder role
168 | * @throws IllegalArgumentException when no folder role with name equal to {@code roleName} exists
169 | * @throws IllegalArgumentException when the {@code sid} is empty
170 | */
171 | public static void assignSidToFolderRole(String sid, String roleName) {
172 | if (StringUtils.isBlank(sid)) {
173 | throw new IllegalArgumentException("Sid should not be blank.");
174 | }
175 |
176 | run(strategy -> {
177 | Set folderRoles = new HashSet<>(strategy.getFolderRoles());
178 | FolderRole role = folderRoles.stream().filter(r -> r.getName().equals(roleName)).findAny().orElseThrow(
179 | () -> new IllegalArgumentException("No folder role with name = \"" + roleName + "\" exists"));
180 | HashSet newSids = new HashSet<>(role.getSids());
181 | newSids.add(sid);
182 | folderRoles.remove(role);
183 | folderRoles.add(new FolderRole(role.getName(), role.getPermissionsUnsorted(), role.getFolderNames(), newSids));
184 | return new FolderBasedAuthorizationStrategy(strategy.getGlobalRoles(), folderRoles, strategy.getAgentRoles());
185 | });
186 | }
187 |
188 | /**
189 | * Deletes the {@link GlobalRole} with name equal to {@code roleName}.
190 | *
191 | * @param roleName the name of the role to be deleted
192 | * @throws IllegalArgumentException when no global role with name equal to {@code roleName} exists
193 | */
194 | public static void deleteGlobalRole(String roleName) {
195 | if (roleName.equals("admin")) {
196 | throw new IllegalArgumentException("Cannot delete the admin role.");
197 | }
198 |
199 | run(strategy -> {
200 | Set globalRoles = new HashSet<>(strategy.getGlobalRoles());
201 | GlobalRole role = globalRoles.stream().filter(r -> r.getName().equals(roleName)).findAny().orElseThrow(
202 | () -> new IllegalArgumentException("No global role with name = \"" + roleName + "\" exists"));
203 | globalRoles.remove(role);
204 | return new FolderBasedAuthorizationStrategy(globalRoles, strategy.getFolderRoles(), strategy.getAgentRoles());
205 | });
206 | }
207 |
208 | /**
209 | * Deletes the {@link FolderRole} with name equal to {@code roleName}.
210 | *
211 | * @param roleName the name of the role to be deleted
212 | * @throws IllegalArgumentException when no role with name equal to {@code roleName} exists
213 | */
214 | public static void deleteFolderRole(String roleName) {
215 | run(strategy -> {
216 | Set folderRoles = new HashSet<>(strategy.getFolderRoles());
217 | FolderRole role = folderRoles.stream().filter(r -> r.getName().equals(roleName)).findAny().orElseThrow(
218 | () -> new IllegalArgumentException("No folder role with name = \"" + roleName + "\" exists"));
219 | folderRoles.remove(role);
220 | return new FolderBasedAuthorizationStrategy(strategy.getGlobalRoles(), folderRoles, strategy.getAgentRoles());
221 | });
222 | }
223 |
224 | /**
225 | * Deletes the {@link AgentRole} with name equal to {@code roleName}.
226 | *
227 | * @param roleName the name of the role to be deleted
228 | * @throws IllegalArgumentException when no role with name equal to {@code roleName} exists
229 | */
230 | public static void deleteAgentRole(String roleName) {
231 | run(strategy -> {
232 | Set agentRoles = new HashSet<>(strategy.getAgentRoles());
233 | AgentRole role = agentRoles.stream().filter(r -> r.getName().equals(roleName)).findAny().orElseThrow(
234 | () -> new IllegalArgumentException("No agent role with name = \"" + roleName + "\" exists"));
235 | agentRoles.remove(role);
236 | return new FolderBasedAuthorizationStrategy(strategy.getGlobalRoles(), strategy.getFolderRoles(), agentRoles);
237 | });
238 | }
239 |
240 | /**
241 | * Removes the {@code sid} from the {@link GlobalRole} with name equal to @{code roleName}.
242 | *
243 | * @param roleName the name of the role.
244 | * @param sid the sid that will be removed.
245 | * @throws IllegalArgumentException when no {@link GlobalRole} with the given {@code roleName} exists.
246 | * @since TODO
247 | */
248 | public static void removeSidFromGlobalRole(String sid, String roleName) {
249 | run(strategy -> {
250 | Set globalRoles = new HashSet<>(strategy.getGlobalRoles());
251 | GlobalRole role = globalRoles.stream().filter(r -> r.getName().equals(roleName)).findAny().orElseThrow(
252 | () -> new IllegalArgumentException("No global role with name equal to \"" + roleName + "\" exists.")
253 | );
254 | Set sids = new HashSet<>(role.getSids());
255 | sids.remove(sid);
256 | globalRoles.remove(role);
257 | globalRoles.add(new GlobalRole(role.getName(), role.getPermissions(), sids));
258 | return new FolderBasedAuthorizationStrategy(globalRoles, strategy.getFolderRoles(), strategy.getAgentRoles());
259 | });
260 | }
261 |
262 | /**
263 | * Removes the {@code sid} from the {@link FolderRole} with name equal to @{code roleName}.
264 | *
265 | * @param roleName the name of the role.
266 | * @param sid the sid that will be removed.
267 | * @throws IllegalArgumentException when no {@link FolderRole} with the given {@code roleName} exists.
268 | * @since TODO
269 | */
270 | public static void removeSidFromFolderRole(String sid, String roleName) {
271 | run(strategy -> {
272 | Set folderRoles = new HashSet<>(strategy.getFolderRoles());
273 | FolderRole role = folderRoles.stream().filter(r -> r.getName().equals(roleName)).findAny().orElseThrow(
274 | () -> new IllegalArgumentException("No folder role with name equal to \"" + roleName + "\" exists.")
275 | );
276 | Set sids = new HashSet<>(role.getSids());
277 | sids.remove(sid);
278 | folderRoles.remove(role);
279 | folderRoles.add(new FolderRole(role.getName(), role.getPermissions(), role.getFolderNames(), sids));
280 | return new FolderBasedAuthorizationStrategy(strategy.getGlobalRoles(), folderRoles, strategy.getAgentRoles());
281 | });
282 | }
283 |
284 | /**
285 | * Removes the {@code sid} from the {@link AgentRole} with name equal to @{code roleName}.
286 | *
287 | * @param roleName the name of the role.
288 | * @param sid the sid that will be removed.
289 | * @throws IllegalArgumentException when no {@link AgentRole} with the given {@code roleName} exists.
290 | * @since TODO
291 | */
292 | public static void removeSidFromAgentRole(String sid, String roleName) {
293 | run(strategy -> {
294 | Set agentRoles = new HashSet<>(strategy.getAgentRoles());
295 | AgentRole role = agentRoles.stream().filter(r -> r.getName().equals(roleName)).findAny().orElseThrow(
296 | () -> new IllegalArgumentException("No agent role with name equal to \"" + roleName + "\" exists.")
297 | );
298 | Set sids = new HashSet<>(role.getSids());
299 | sids.remove(sid);
300 | agentRoles.remove(role);
301 | agentRoles.add(new AgentRole(role.getName(), role.getPermissions(), role.getAgents(), sids));
302 | return new FolderBasedAuthorizationStrategy(strategy.getGlobalRoles(), strategy.getFolderRoles(), agentRoles);
303 | });
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/folderauth/FolderAuthorizationStrategyManagementLink/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ${%helpNeeded}
13 |
14 |
15 | ${%docs}
16 |
17 |
18 |
${%manageGlobalRoles}
19 |
20 | ${%currentGlobalRoles}
21 |
22 |
23 |
24 |
25 |
26 |
27 | ${%name}: ${globalRole.name}
28 |
29 | ${%sids}: ${globalRole.getSidsCommaSeparated()}
30 |
48 |
${%viewPermissions}
49 |
50 |
51 |
52 |
53 | ${wrapper.permission.group.title}/${wrapper.permission.name}
54 |
55 |
56 |
57 |
58 |
63 |
64 |
65 |
66 |
67 |
68 | ${%addGlobalRole}
69 |
70 |
71 |
72 |
73 | ${%roleName}
74 |
75 |
76 |
77 |
78 | ${%permissions}
79 |
80 |
81 |
82 |
83 | ${perm.group.title}/${perm.name}
84 |
85 |
86 |
87 |
88 | ${%addRole}
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | ${%manageFolderRoles}
98 |
99 |
100 |
101 |
102 | ${%emptyFolderRoles}
103 |
104 |
105 |
106 |
107 | ${%currentFolderRoles}
108 |
109 |
110 |
111 |
112 |
113 |
114 | ${%name}: ${folderRole.name}
115 |
116 | ${%sids}: ${folderRole.getSidsCommaSeparated()}
117 |
118 | ${%folders}: ${folderRole.getFolderNamesCommaSeparated()}
119 |
120 |
138 |
${%viewPermissions}
139 |
140 |
141 |
142 |
143 | ${wrapper.permission.group.title}/${wrapper.permission.name}
144 |
145 |
146 |
147 |
148 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | ${%addFolderRole}
161 |
162 |
163 |
164 |
165 | ${%roleName}:
166 |
167 |
168 |
169 |
184 |
185 | ${%permissions}
186 |
187 |
188 |
189 |
190 | ${perm.group.title}/${perm.name}
191 |
192 |
193 |
194 |
195 |
197 | ${%addRole}
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 | ${%manageAgentRoles}
208 |
209 |
210 |
211 |
212 | ${%emptyAgentRoles}
213 |
214 |
215 |
216 |
217 | ${%currentAgentRoles}
218 |
219 |
220 |
221 |
222 |
223 |
224 | ${%name}: ${agentRole.name}
225 |
226 | ${%sids}: ${agentRole.getSidsCommaSeparated()}
227 |
228 | ${%agents}: ${agentRole.getAgentNamesCommaSeparated()}
229 |
230 |
248 |
${%viewPermissions}
249 |
250 |
251 |
252 |
253 | ${wrapper.permission.group.title}/${wrapper.permission.name}
254 |
255 |
256 |
257 |
258 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 | ${%addAgentRole}
271 |
272 |
273 |
274 |
275 | ${%roleName}:
276 |
277 |
278 |
279 |
280 |
281 | ${%applyOn}:
282 |
283 |
284 |
285 | ${agent.displayName}
286 |
287 |
288 |
289 |
290 | ${%permissions}
291 |
292 |
293 |
294 |
295 | ${perm.group.title}/${perm.name}
296 |
297 |
298 |
299 |
300 | ${%addRole}
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/folderauth/FolderAuthorizationStrategyManagementLink.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.folderauth;
2 |
3 | import com.cloudbees.hudson.plugins.folder.AbstractFolder;
4 | import hudson.Extension;
5 | import hudson.model.AbstractItem;
6 | import hudson.model.Api;
7 | import hudson.model.Computer;
8 | import hudson.model.Hudson;
9 | import hudson.model.Item;
10 | import hudson.model.ManagementLink;
11 | import hudson.model.Run;
12 | import hudson.model.View;
13 | import hudson.scm.SCM;
14 | import hudson.security.ACL;
15 | import hudson.security.ACLContext;
16 | import hudson.security.AuthorizationStrategy;
17 | import hudson.security.Permission;
18 | import hudson.security.PermissionGroup;
19 | import io.jenkins.plugins.folderauth.misc.AgentRoleCreationRequest;
20 | import io.jenkins.plugins.folderauth.misc.FolderRoleCreationRequest;
21 | import io.jenkins.plugins.folderauth.misc.GlobalRoleCreationRequest;
22 | import io.jenkins.plugins.folderauth.misc.PermissionWrapper;
23 | import io.jenkins.plugins.folderauth.roles.AgentRole;
24 | import io.jenkins.plugins.folderauth.roles.FolderRole;
25 | import io.jenkins.plugins.folderauth.roles.GlobalRole;
26 | import jenkins.model.Jenkins;
27 | import net.sf.json.JSONArray;
28 | import org.kohsuke.accmod.Restricted;
29 | import org.kohsuke.accmod.restrictions.NoExternalUse;
30 | import org.kohsuke.stapler.QueryParameter;
31 | import org.kohsuke.stapler.Stapler;
32 | import org.kohsuke.stapler.export.ExportedBean;
33 | import org.kohsuke.stapler.interceptor.RequirePOST;
34 | import org.kohsuke.stapler.json.JsonBody;
35 | import org.kohsuke.stapler.verb.GET;
36 |
37 | import javax.annotation.CheckForNull;
38 | import javax.annotation.Nonnull;
39 | import javax.annotation.ParametersAreNonnullByDefault;
40 | import javax.servlet.ServletException;
41 | import java.io.IOException;
42 | import java.util.Arrays;
43 | import java.util.HashSet;
44 | import java.util.List;
45 | import java.util.Set;
46 | import java.util.SortedSet;
47 | import java.util.TreeSet;
48 | import java.util.logging.Level;
49 | import java.util.logging.Logger;
50 | import java.util.stream.Collectors;
51 |
52 | @Extension
53 | @ExportedBean
54 | @ParametersAreNonnullByDefault
55 | public class FolderAuthorizationStrategyManagementLink extends ManagementLink {
56 | private static final Logger LOGGER = Logger.getLogger(FolderAuthorizationStrategyManagementLink.class.getName());
57 |
58 | @CheckForNull
59 | @Override
60 | public String getIconFileName() {
61 | return Jenkins.get().getAuthorizationStrategy() instanceof FolderBasedAuthorizationStrategy ?
62 | "lock.png" : null;
63 | }
64 |
65 | /**
66 | * {@inheritDoc}
67 | */
68 | @Nonnull
69 | @Override
70 | public String getDescription() {
71 | return Messages.FolderBasedAuthorizationStrategy_Description();
72 | }
73 |
74 | @CheckForNull
75 | @Override
76 | public String getUrlName() {
77 | return "folder-auth";
78 | }
79 |
80 | @CheckForNull
81 | @Override
82 | public String getDisplayName() {
83 | return Messages.FolderBasedAuthorizationStrategy_DisplayName();
84 | }
85 |
86 | /**
87 | * Name of the category for this management link. Exists so that plugins with core dependency pre-dating the version
88 | * when this was introduced can define a category.
89 | *
90 | * TODO when the core version is >2.226 change this to override {@code getCategory()} instead
91 | *
92 | * @return name of the desired category, one of the enum values of Category, e.g. {@code STATUS}.
93 | * @since 2.226
94 | */
95 | public String getCategoryName() {
96 | return "SECURITY";
97 | }
98 |
99 | @Nonnull
100 | @Restricted(NoExternalUse.class)
101 | @SuppressWarnings("unused") // used by index.jelly
102 | public Set getGlobalPermissions() {
103 | HashSet groups = new HashSet<>(PermissionGroup.getAll());
104 | groups.remove(PermissionGroup.get(Permission.class));
105 | return getSafePermissions(groups);
106 | }
107 |
108 | @Nonnull
109 | @Restricted(NoExternalUse.class)
110 | @SuppressWarnings("unused") // used by index.jelly
111 | public Set getFolderPermissions() {
112 | HashSet groups = new HashSet<>(PermissionGroup.getAll());
113 | groups.remove(PermissionGroup.get(Hudson.class));
114 | groups.remove(PermissionGroup.get(Computer.class));
115 | groups.remove(PermissionGroup.get(Permission.class));
116 | return getSafePermissions(groups);
117 | }
118 |
119 | @Nonnull
120 | @Restricted(NoExternalUse.class)
121 | @SuppressWarnings("unused") // used by index.jelly
122 | public Set getAgentPermissions() {
123 | HashSet groups = new HashSet<>(PermissionGroup.getAll());
124 | groups.remove(PermissionGroup.get(Run.class));
125 | groups.remove(PermissionGroup.get(SCM.class));
126 | groups.remove(PermissionGroup.get(View.class));
127 | groups.remove(PermissionGroup.get(Item.class));
128 | groups.remove(PermissionGroup.get(Hudson.class));
129 | groups.remove(PermissionGroup.get(Permission.class));
130 | return getSafePermissions(groups);
131 | }
132 |
133 | /**
134 | * Returns the {@link Api} for the plugin.
135 | *
136 | * @return Api for the plugin.
137 | * @see
138 | * Wiki Article on exposing data to remote API .
139 | */
140 | public Api getApi() {
141 | return new Api(this);
142 | }
143 |
144 | /**
145 | * Adds a {@link GlobalRole} to {@link FolderBasedAuthorizationStrategy}.
146 | *
147 | * @param request the request to create the {@link GlobalRole}
148 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
149 | * not {@link FolderBasedAuthorizationStrategy}
150 | */
151 | @RequirePOST
152 | @Restricted(NoExternalUse.class)
153 | public void doAddGlobalRole(@JsonBody GlobalRoleCreationRequest request) {
154 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
155 | FolderAuthorizationStrategyAPI.addGlobalRole(request.getGlobalRole());
156 | }
157 |
158 | /**
159 | * Assigns {@code sid} to the global role identified by {@code roleName}.
160 | *
161 | *
162 | * @param roleName the name of the global to which {@code sid} will be assigned to.
163 | * @param sid the sid of the user/group to be assigned.
164 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
165 | * not {@link FolderBasedAuthorizationStrategy}
166 | * @throws IllegalArgumentException when no role with name equal to {@code roleName} exists.
167 | */
168 | @RequirePOST
169 | @Restricted(NoExternalUse.class)
170 | public void doAssignSidToGlobalRole(@QueryParameter(required = true) String roleName,
171 | @QueryParameter(required = true) String sid) {
172 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
173 | FolderAuthorizationStrategyAPI.assignSidToGlobalRole(sid, roleName);
174 | redirect();
175 | }
176 |
177 | /**
178 | * Adds a {@link FolderRole} to {@link FolderBasedAuthorizationStrategy}.
179 | *
180 | * @param request the request to create the role
181 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
182 | * not {@link FolderBasedAuthorizationStrategy}
183 | */
184 | @RequirePOST
185 | @Restricted(NoExternalUse.class)
186 | public void doAddFolderRole(@JsonBody FolderRoleCreationRequest request) {
187 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
188 | FolderAuthorizationStrategyAPI.addFolderRole(request.getFolderRole());
189 | }
190 |
191 | /**
192 | * Adds an {@link AgentRole} to {@link FolderBasedAuthorizationStrategy}.
193 | *
194 | * @param request the request to create the role
195 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
196 | * not {@link FolderBasedAuthorizationStrategy}
197 | */
198 | @RequirePOST
199 | @Restricted(NoExternalUse.class)
200 | public void doAddAgentRole(@JsonBody AgentRoleCreationRequest request) {
201 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
202 | FolderAuthorizationStrategyAPI.addAgentRole(request.getAgentRole());
203 | }
204 |
205 | /**
206 | * Assigns {@code sid} to the folder role identified by {@code roleName}.
207 | *
208 | *
209 | * @param roleName the name of the global to which {@code sid} will be assigned to.
210 | * @param sid the sid of the user/group to be assigned.
211 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
212 | * not {@link FolderBasedAuthorizationStrategy}
213 | * @throws java.util.NoSuchElementException when no role with name equal to {@code roleName} exists.
214 | */
215 | @RequirePOST
216 | @Restricted(NoExternalUse.class)
217 | public void doAssignSidToFolderRole(@QueryParameter(required = true) String roleName,
218 | @QueryParameter(required = true) String sid) {
219 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
220 | FolderAuthorizationStrategyAPI.assignSidToFolderRole(sid, roleName);
221 | redirect();
222 | }
223 |
224 | /**
225 | * Assigns {@code sid} to the {@link AgentRole} identified by {@code roleName}.
226 | *
227 | *
228 | * @param roleName the name of the global to which {@code sid} will be assigned to.
229 | * @param sid the sid of the user/group to be assigned.
230 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
231 | * not {@link FolderBasedAuthorizationStrategy}
232 | * @throws IllegalArgumentException when no role with name equal to {@code roleName} exists.
233 | */
234 | @RequirePOST
235 | @Restricted(NoExternalUse.class)
236 | public void doAssignSidToAgentRole(@QueryParameter(required = true) String roleName,
237 | @QueryParameter(required = true) String sid) {
238 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
239 | FolderAuthorizationStrategyAPI.assignSidToAgentRole(sid, roleName);
240 | redirect();
241 | }
242 |
243 | /**
244 | * Redirects to the same page that initiated the request.
245 | */
246 | private void redirect() {
247 | try {
248 | Stapler.getCurrentResponse().forwardToPreviousPage(Stapler.getCurrentRequest());
249 | } catch (ServletException | IOException e) {
250 | LOGGER.log(Level.WARNING, "Unable to redirect to previous page.");
251 | }
252 | }
253 |
254 | @Nonnull
255 | @Restricted(NoExternalUse.class)
256 | @SuppressWarnings("unused") // used by index.jelly
257 | public SortedSet getGlobalRoles() {
258 | AuthorizationStrategy strategy = Jenkins.get().getAuthorizationStrategy();
259 | if (strategy instanceof FolderBasedAuthorizationStrategy) {
260 | return new TreeSet<>(((FolderBasedAuthorizationStrategy) strategy).getGlobalRoles());
261 | } else {
262 | throw new IllegalStateException(Messages.FolderBasedAuthorizationStrategy_NotCurrentStrategy());
263 | }
264 | }
265 |
266 | /**
267 | * Get all {@link AbstractFolder}s in the system
268 | *
269 | * @return full names of all {@link AbstractFolder}s in the system
270 | */
271 | @GET
272 | @Nonnull
273 | @Restricted(NoExternalUse.class)
274 | public JSONArray doGetAllFolders() {
275 | Jenkins jenkins = Jenkins.get();
276 | jenkins.checkPermission(Jenkins.ADMINISTER);
277 | List folders;
278 |
279 | try (ACLContext ignored = ACL.as(ACL.SYSTEM)) {
280 | folders = jenkins.getAllItems(AbstractFolder.class);
281 | }
282 |
283 | return JSONArray.fromObject(folders.stream().map(AbstractItem::getFullName).collect(Collectors.toList()));
284 | }
285 |
286 | /**
287 | * Get all {@link Computer}s in the system
288 | *
289 | * @return all Computers in the system
290 | */
291 | @Nonnull
292 | @Restricted(NoExternalUse.class)
293 | @SuppressWarnings("unused") // used by index.jelly
294 | public List getAllComputers() {
295 | Jenkins jenkins = Jenkins.get();
296 | jenkins.checkPermission(Jenkins.ADMINISTER);
297 | Computer[] computers;
298 |
299 | try (ACLContext ignored = ACL.as(ACL.SYSTEM)) {
300 | computers = jenkins.getComputers();
301 | }
302 |
303 | return Arrays.asList(computers);
304 | }
305 |
306 | /**
307 | * Returns the {@link FolderRole}s used by the {@link FolderBasedAuthorizationStrategy}.
308 | *
309 | * @return the {@link FolderRole}s used by the {@link FolderBasedAuthorizationStrategy}
310 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
311 | * not {@link FolderBasedAuthorizationStrategy}
312 | */
313 | @Nonnull
314 | @Restricted(NoExternalUse.class)
315 | @SuppressWarnings("unused") // used by index.jelly
316 | public SortedSet getFolderRoles() {
317 | AuthorizationStrategy strategy = Jenkins.get().getAuthorizationStrategy();
318 | if (strategy instanceof FolderBasedAuthorizationStrategy) {
319 | return new TreeSet<>(((FolderBasedAuthorizationStrategy) strategy).getFolderRoles());
320 | } else {
321 | throw new IllegalStateException(Messages.FolderBasedAuthorizationStrategy_NotCurrentStrategy());
322 | }
323 | }
324 |
325 | @Nonnull
326 | @Restricted(NoExternalUse.class)
327 | @SuppressWarnings("unused") // used by index.jelly
328 | public SortedSet getAgentRoles() {
329 | AuthorizationStrategy strategy = Jenkins.get().getAuthorizationStrategy();
330 | if (strategy instanceof FolderBasedAuthorizationStrategy) {
331 | return new TreeSet<>(((FolderBasedAuthorizationStrategy) strategy).getAgentRoles());
332 | } else {
333 | throw new IllegalStateException(Messages.FolderBasedAuthorizationStrategy_NotCurrentStrategy());
334 | }
335 | }
336 |
337 | /**
338 | * Deletes a global role.
339 | *
340 | * @param roleName the name of the role to be deleted
341 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
342 | * not {@link FolderBasedAuthorizationStrategy}
343 | * @throws IllegalArgumentException when trying to delete the admin role
344 | * @throws IllegalArgumentException when no role with name equal to {@code roleName} exists.
345 | */
346 | @RequirePOST
347 | @Restricted(NoExternalUse.class)
348 | public void doDeleteGlobalRole(@QueryParameter(required = true) String roleName) {
349 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
350 | FolderAuthorizationStrategyAPI.deleteGlobalRole(roleName);
351 | redirect();
352 | }
353 |
354 | /**
355 | * Deletes a folder role.
356 | *
357 | * @param roleName the name of the role to be deleted
358 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
359 | * not {@link FolderBasedAuthorizationStrategy}
360 | * @throws IllegalArgumentException when no role with name equal to {@code roleName} exists.
361 | */
362 | @RequirePOST
363 | @Restricted(NoExternalUse.class)
364 | public void doDeleteFolderRole(@QueryParameter(required = true) String roleName) {
365 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
366 | FolderAuthorizationStrategyAPI.deleteFolderRole(roleName);
367 | redirect();
368 | }
369 |
370 | /**
371 | * Deletes an {@link AgentRole} from the {@link FolderBasedAuthorizationStrategy}.
372 | *
373 | * @param roleName the name of the role to be deleted
374 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
375 | * not {@link FolderBasedAuthorizationStrategy}
376 | * @throws java.util.NoSuchElementException when no role with name equal to {@code roleName} exists.
377 | */
378 | @RequirePOST
379 | @Restricted(NoExternalUse.class)
380 | public void doDeleteAgentRole(@QueryParameter(required = true) String roleName) {
381 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
382 | FolderAuthorizationStrategyAPI.deleteAgentRole(roleName);
383 | redirect();
384 | }
385 |
386 | @Nonnull
387 | static Set getSafePermissions(Set groups) {
388 | TreeSet safePermissions = new TreeSet<>(Permission.ID_COMPARATOR);
389 | groups.stream().map(PermissionGroup::getPermissions).forEach(safePermissions::addAll);
390 | safePermissions.removeAll(PermissionWrapper.DANGEROUS_PERMISSIONS);
391 | return safePermissions;
392 | }
393 |
394 | /**
395 | * Removes {@code sid} from the global role identified by {@code roleName}.
396 | *
397 | * @param roleName the name of the global role from which {@code sid} will be removed.
398 | * @param sid the sid of the user/group to be assigned.
399 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
400 | * not {@link FolderBasedAuthorizationStrategy}
401 | * @throws IllegalArgumentException when no role with name equal to {@code roleName} exists.
402 | */
403 | @RequirePOST
404 | @Restricted(NoExternalUse.class)
405 | public void doRemoveSidFromGlobalRole(@QueryParameter(required = true) String roleName,
406 | @QueryParameter(required = true) String sid) {
407 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
408 | FolderAuthorizationStrategyAPI.removeSidFromGlobalRole(sid, roleName);
409 | redirect();
410 | }
411 |
412 | /**
413 | * Removes {@code sid} from the folder role identified by {@code roleName}.
414 | *
415 | * @param roleName the name of the folder role from which {@code sid} will be removed.
416 | * @param sid the sid of the user/group to be assigned.
417 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
418 | * not {@link FolderBasedAuthorizationStrategy}
419 | * @throws IllegalArgumentException when no role with name equal to {@code roleName} exists.
420 | */
421 | @RequirePOST
422 | @Restricted(NoExternalUse.class)
423 | public void doRemoveSidFromFolderRole(@QueryParameter(required = true) String roleName,
424 | @QueryParameter(required = true) String sid) {
425 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
426 | FolderAuthorizationStrategyAPI.removeSidFromFolderRole(sid, roleName);
427 | redirect();
428 | }
429 | /**
430 | * Removes {@code sid} from the agent role identified by {@code roleName}.
431 | *
432 | * @param roleName the name of the agent from which {@code sid} will be removed.
433 | * @param sid the sid of the user/group to be assigned.
434 | * @throws IllegalStateException when {@link Jenkins#getAuthorizationStrategy()} is
435 | * not {@link FolderBasedAuthorizationStrategy}
436 | * @throws IllegalArgumentException when no role with name equal to {@code roleName} exists.
437 | */
438 | @RequirePOST
439 | @Restricted(NoExternalUse.class)
440 | public void doRemoveSidFromAgentRole(@QueryParameter(required = true) String roleName,
441 | @QueryParameter(required = true) String sid) {
442 | Jenkins.get().checkPermission(Jenkins.ADMINISTER);
443 | FolderAuthorizationStrategyAPI.removeSidFromAgentRole(sid, roleName);
444 | redirect();
445 | }
446 | }
447 |
--------------------------------------------------------------------------------