Skip to content

Commit

Permalink
add REST API for templates (#316)
Browse files Browse the repository at this point in the history
It is now possible to add/update and removing templates via the REST API
When adding/updating a role you can now specify the template to use.
  • Loading branch information
mawinter69 authored Jul 17, 2023
1 parent ae39b13 commit d7f94d9
Show file tree
Hide file tree
Showing 5 changed files with 356 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,13 @@ public PermissionTemplate(Set<Permission> permissions, String name) {
}

/**
* Checks whether the template is used by one or more roles.#
* Checks whether the template is used by one or more roles.
*
* @return true when template is used.
*/
public boolean isUsed() {
AuthorizationStrategy auth = Jenkins.get().getAuthorizationStrategy();
ProjectNamingStrategy pns = Jenkins.get().getProjectNamingStrategy();
if (auth instanceof RoleBasedAuthorizationStrategy && pns instanceof RoleBasedProjectNamingStrategy) {
if (auth instanceof RoleBasedAuthorizationStrategy) {
RoleBasedAuthorizationStrategy rbas = (RoleBasedAuthorizationStrategy) auth;
Map<Role, Set<PermissionEntry>> roleMap = rbas.getGrantedRolesEntries(RoleType.Project);
for (Role role : roleMap.keySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import hudson.Util;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
Expand All @@ -38,6 +39,8 @@
import java.util.regex.Pattern;
import org.apache.commons.collections.CollectionUtils;
import org.jenkinsci.plugins.rolestrategy.permissions.PermissionHelper;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;

/**
Expand Down Expand Up @@ -198,9 +201,13 @@ private void setPermissions(Set<Permission> permissions) {
}

/**
* Updates the permissions from the used template.
* Updates the permissions from the template matching the name.
*
* @param permissionTemplates List of templates to look for
* @deprecated Use {@link #refreshPermissionsFromTemplate(PermissionTemplate)}
*/
public void refreshPermissionsFromTemplate(Set<PermissionTemplate> permissionTemplates) {
@Deprecated
public void refreshPermissionsFromTemplate(Collection<PermissionTemplate> permissionTemplates) {
if (Util.fixEmptyAndTrim(templateName) != null) {
boolean found = false;
for (PermissionTemplate pt : permissionTemplates) {
Expand All @@ -216,6 +223,20 @@ public void refreshPermissionsFromTemplate(Set<PermissionTemplate> permissionTem
}
}

/**
* Updates the permissions from the given template.
*
* The name of the given template must match the configured template name in the role.
*
* @param permissionTemplate PermissionTemplate
*/
@Restricted(NoExternalUse.class)
public void refreshPermissionsFromTemplate(@CheckForNull PermissionTemplate permissionTemplate) {
if (permissionTemplate != null && templateName != null && templateName.equals(permissionTemplate.getName())) {
setPermissions(permissionTemplate.getPermissions());
}
}

/**
* Gets the role description.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,18 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.acegisecurity.acls.sid.PrincipalSid;
Expand All @@ -94,6 +97,7 @@
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.kohsuke.stapler.verb.GET;
import org.kohsuke.stapler.verb.POST;

/**
* Role-based authorization strategy.
Expand All @@ -115,7 +119,7 @@ public class RoleBasedAuthorizationStrategy extends AuthorizationStrategy {
private final RoleMap agentRoles;
private final RoleMap globalRoles;
private final RoleMap itemRoles;
private Set<PermissionTemplate> permissionTemplates;
private Map<String, PermissionTemplate> permissionTemplates;

/**
* Create new RoleBasedAuthorizationStrategy.
Expand All @@ -124,7 +128,7 @@ public RoleBasedAuthorizationStrategy() {
agentRoles = new RoleMap();
globalRoles = new RoleMap();
itemRoles = new RoleMap();
permissionTemplates = new TreeSet<>();
permissionTemplates = new TreeMap<>();
}

/**
Expand All @@ -143,7 +147,13 @@ public RoleBasedAuthorizationStrategy(Map<String, RoleMap> grantedRoles) {
* @param permissionTemplates the permission templates in the strategy
*/
public RoleBasedAuthorizationStrategy(Map<String, RoleMap> grantedRoles, @CheckForNull Set<PermissionTemplate> permissionTemplates) {
this.permissionTemplates = permissionTemplates == null ? Collections.emptySet() : new TreeSet<>(permissionTemplates);

this.permissionTemplates = new TreeMap<>();
if (permissionTemplates != null) {
for (PermissionTemplate template : permissionTemplates) {
this.permissionTemplates.put(template.getName(), template);
}
}

RoleMap map = grantedRoles.get(SLAVE);
agentRoles = map == null ? new RoleMap() : map;
Expand All @@ -162,7 +172,9 @@ public RoleBasedAuthorizationStrategy(Map<String, RoleMap> grantedRoles, @CheckF
private void refreshPermissionsFromTemplate() {
SortedMap<Role, Set<PermissionEntry>> roles = getGrantedRolesEntries(RoleBasedAuthorizationStrategy.PROJECT);
for (Role role : roles.keySet()) {
role.refreshPermissionsFromTemplate(this.permissionTemplates);
if (Util.fixEmptyAndTrim(role.getTemplateName()) != null) {
role.refreshPermissionsFromTemplate(permissionTemplates.get(role.getTemplateName()));
}
}
}

Expand Down Expand Up @@ -284,7 +296,16 @@ public SortedMap<Role, Set<String>> getGrantedRoles(@NonNull RoleType type) {
* @return set of permission templates.
*/
public Set<PermissionTemplate> getPermissionTemplates() {
return Collections.unmodifiableSet(permissionTemplates);
return Set.copyOf(permissionTemplates.values());
}

@CheckForNull
public PermissionTemplate getPermissionTemplate(String templateName) {
return permissionTemplates.get(templateName);
}

public boolean hasPermissionTemplate(String name) {
return permissionTemplates.containsKey(name);
}

/**
Expand Down Expand Up @@ -391,11 +412,76 @@ private static void checkAdminPerm() {
instance().checkPermission(Jenkins.ADMINISTER);
}

/**
* API method to add a permission template.
*
* An existing template with the same will only be replaced when overwrite is set. Otherwise, the request will fail with status
* <code>400</code>
*
* @param name The template nae
* @param permissionIds Comma separated list of permission IDs
* @param overwrite If an existing template should be overwritten
*/
@POST
@Restricted(NoExternalUse.class)
public void doAddTemplate(@QueryParameter(required = true) String name,
@QueryParameter(required = true) String permissionIds,
@QueryParameter(required = false) boolean overwrite)
throws IOException {
checkAdminPerm();
List<String> permissionList = Arrays.asList(permissionIds.split(","));
Set<Permission> permissionSet = PermissionHelper.fromStrings(permissionList, true);
PermissionTemplate template = new PermissionTemplate(permissionSet, name);
if (!overwrite && hasPermissionTemplate(name)) {
Stapler.getCurrentResponse().sendError(HttpServletResponse.SC_BAD_REQUEST, "A template with name " + name + " already exists.");
return;
}
permissionTemplates.put(name, template);
refreshPermissionsFromTemplate();
persistChanges();
}

/**
* API method to remove templates.
*
* <p>
* Example: {@code curl -X POST localhost:8080/role-strategy/strategy/removeTemplates --data "templates=developer,qualits"}
*
* @param names comma separated list of templates to remove
* @param force If templates that are in use should be removed
* @throws IOException in case saving changes fails
*/
@POST
@Restricted(NoExternalUse.class)
public void doRemoveTemplates(@QueryParameter(required = true) String names,
@QueryParameter(required = false) boolean force) throws IOException {
checkAdminPerm();
String[] split = names.split(",");
for (String templateName : split) {
templateName = templateName.trim();
PermissionTemplate pt = getPermissionTemplate(templateName);
if (pt != null && (!pt.isUsed() || force)) {
permissionTemplates.remove(templateName);
RoleMap roleMap = getRoleMap(RoleType.Project);
for (Role role : roleMap.getRoles()) {
if (templateName.equals(role.getTemplateName())) {
role.setTemplateName(null);
}
}
}
}
persistChanges();
}

/**
* API method to add a role.
*
* <p>Unknown and dangerous permissions are ignored.
*
* When specifying a <code>template</code> for an item role, the given permissions are ignored. The named template must exist,
* otherwise the request fails with status <code>400</code>.
* The <code>template</code> is ignored when adding global or agent roles.
*
* <p>Example:
* {@code curl -X POST localhost:8080/role-strategy/strategy/addRole --data "type=globalRoles&amp;roleName=ADM&amp;
* permissionIds=hudson.model.Item.Discover,hudson.model.Item.ExtendedRead&amp;overwrite=true"}
Expand All @@ -406,6 +492,7 @@ private static void checkAdminPerm() {
* @param permissionIds Comma separated list of IDs for given roleName
* @param overwrite Overwrite existing role
* @param pattern Role pattern
* @param template Name of template
* @throws IOException In case saving changes fails
* @since 2.5.0
*/
Expand All @@ -415,20 +502,31 @@ public void doAddRole(@QueryParameter(required = true) String type,
@QueryParameter(required = true) String roleName,
@QueryParameter(required = true) String permissionIds,
@QueryParameter(required = true) String overwrite,
@QueryParameter(required = false) String pattern) throws IOException {
@QueryParameter(required = false) String pattern,
@QueryParameter(required = false) String template) throws IOException {
checkAdminPerm();

boolean overwriteb = Boolean.parseBoolean(overwrite);
final boolean overwriteb = Boolean.parseBoolean(overwrite);
String pttrn = ".*";
String templateName = Util.fixEmptyAndTrim(template);

if (!type.equals(RoleBasedAuthorizationStrategy.GLOBAL) && pattern != null) {
pttrn = pattern;
}

List<String> permissionList = Arrays.asList(permissionIds.split(","));

Set<Permission> permissionSet = PermissionHelper.fromStrings(permissionList, true);

Role role = new Role(roleName, pttrn, permissionSet);

if (RoleBasedAuthorizationStrategy.PROJECT.equals(type) && templateName != null) {
if (!hasPermissionTemplate(template)) {
Stapler.getCurrentResponse().sendError(HttpServletResponse.SC_BAD_REQUEST, "A template with name " + template + " doesn't exists.");
return;
}
role.setTemplateName(templateName);
role.refreshPermissionsFromTemplate(getPermissionTemplate(templateName));
}

RoleType roleType = RoleType.fromString(type);
if (overwriteb) {
RoleMap roleMap = getRoleMap(roleType);
Expand Down Expand Up @@ -709,12 +807,58 @@ public void doUnassignGroupRole(@QueryParameter(required = true) String type,
persistChanges();
}

/**
* API method to get the granted permissions of a template and if the template is used.
*
* <p>
* Example: {@code curl -XGET 'http://localhost:8080/jenkins/role-strategy/strategy/getTemplate?name=developer'}
*
* <p>
* Returns json with granted permissions and assigned sids.<br>
* Example:
*
* <pre>{@code
* {
* "permissionIds": {
* "hudson.model.Item.Read":true,
* "hudson.model.Item.Build":true,
* "hudson.model.Item.Cancel":true,
* },
* "isUsed": true
* }
* }
* </pre>
*
*/
@GET
@Restricted(NoExternalUse.class)
public void doGetTemplate(@QueryParameter(required = true) String name) throws IOException {
checkAdminPerm();
JSONObject responseJson = new JSONObject();

PermissionTemplate template = permissionTemplates.get(name);
if (template != null) {
Set<Permission> permissions = template.getPermissions();
Map<String, Boolean> permissionsMap = new HashMap<>();
for (Permission permission : permissions) {
permissionsMap.put(permission.getId(), permission.getEnabled());
}
responseJson.put("permissionIds", permissionsMap);
responseJson.put("isUsed", template.isUsed());
}
Stapler.getCurrentResponse().setContentType("application/json;charset=UTF-8");
Writer writer = Stapler.getCurrentResponse().getCompressedWriter(Stapler.getCurrentRequest());
responseJson.write(writer);
writer.close();

}

/**
* API method to get the granted permissions of a role and the SIDs assigned to it.
*
* <p>
* Example: {@code curl -XGET 'http://localhost:8080/jenkins/role-strategy/strategy/getRole
* ?type=globalRoles&roleName=admin'}
* ?type=projectRoles&roleName=admin'}
*
* <p>
* Returns json with granted permissions and assigned sids.<br>
Expand All @@ -723,11 +867,13 @@ public void doUnassignGroupRole(@QueryParameter(required = true) String type,
* <pre>{@code
* {
* "permissionIds": {
* "hudson.model.Hudson.Read":true,
* "hudson.model.Item.Read":true,
* "hudson.model.Item.Build":true,
* "hudson.model.Item.Cancel":true,
* },
* "sids": [{"type":"USER","sid":"user1"}, {"type":"USER","sid":"user2"}]
* "pattern": ".*",
* "template": "developers",
* }
* }
* </pre>
Expand Down Expand Up @@ -758,6 +904,9 @@ public void doGetRole(@QueryParameter(required = true) String type,
}
Map<Role, Set<PermissionEntry>> grantedRoleMap = roleMap.getGrantedRolesEntries();
responseJson.put("sids", grantedRoleMap.get(role));
if (type.equals(RoleBasedAuthorizationStrategy.PROJECT)) {
responseJson.put("template", role.getTemplateName());
}
}

Stapler.getCurrentResponse().setContentType("application/json;charset=UTF-8");
Expand Down Expand Up @@ -909,7 +1058,7 @@ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingC
RoleBasedAuthorizationStrategy strategy = (RoleBasedAuthorizationStrategy) source;

writer.startNode(PERMISSION_TEMPLATES);
for (PermissionTemplate permissionTemplate : strategy.permissionTemplates) {
for (PermissionTemplate permissionTemplate : strategy.permissionTemplates.values()) {
writer.startNode("template");
writer.addAttribute("name", permissionTemplate.getName());
writer.startNode("permissions");
Expand Down Expand Up @@ -1192,7 +1341,7 @@ public void doTemplatesSubmit(StaplerRequest req, StaplerResponse rsp) throws Se
RoleBasedAuthorizationStrategy strategy = (RoleBasedAuthorizationStrategy) oldStrategy;

JSONObject permissionTemplatesJson = json.getJSONObject(PERMISSION_TEMPLATES);
Set<PermissionTemplate> permissionTemplates = new TreeSet<>();
Map<String, PermissionTemplate> permissionTemplates = new TreeMap<>();
for (Map.Entry<String, JSONObject> r : (Set<Map.Entry<String, JSONObject>>)
permissionTemplatesJson.getJSONObject("data").entrySet()) {
String templateName = r.getKey();
Expand All @@ -1203,7 +1352,7 @@ public void doTemplatesSubmit(StaplerRequest req, StaplerResponse rsp) throws Se
}
}
PermissionTemplate permissionTemplate = new PermissionTemplate(templateName, permissionStrings);
permissionTemplates.add(permissionTemplate);
permissionTemplates.put(templateName, permissionTemplate);
}

strategy.permissionTemplates = permissionTemplates;
Expand Down
Loading

0 comments on commit d7f94d9

Please sign in to comment.