Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add REST API for templates #316

Merged
merged 2 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,13 @@
}

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

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 68 is only partially covered, one branch is missing
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 @@
}

/**
* 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 @@
}
}

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

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 235 is only partially covered, 3 branches are missing
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 @@
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 @@
agentRoles = new RoleMap();
globalRoles = new RoleMap();
itemRoles = new RoleMap();
permissionTemplates = new TreeSet<>();
permissionTemplates = new TreeMap<>();
}

/**
Expand All @@ -143,7 +147,13 @@
* @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 @@
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 @@
* @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 @@
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)) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 435 is only partially covered, one branch is missing
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 @@
* @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 @@
@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 @@
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) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 840 is only partially covered, one branch is missing
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 @@
* <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 @@
}
Map<Role, Set<PermissionEntry>> grantedRoleMap = roleMap.getGrantedRolesEntries();
responseJson.put("sids", grantedRoleMap.get(role));
if (type.equals(RoleBasedAuthorizationStrategy.PROJECT)) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 907 is only partially covered, one branch is missing
responseJson.put("template", role.getTemplateName());

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 908 is not covered by tests
}
}

Stapler.getCurrentResponse().setContentType("application/json;charset=UTF-8");
Expand Down Expand Up @@ -909,7 +1058,7 @@
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 @@
RoleBasedAuthorizationStrategy strategy = (RoleBasedAuthorizationStrategy) oldStrategy;

JSONObject permissionTemplatesJson = json.getJSONObject(PERMISSION_TEMPLATES);
Set<PermissionTemplate> permissionTemplates = new TreeSet<>();
Map<String, PermissionTemplate> permissionTemplates = new TreeMap<>();

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 1344 is not covered by tests
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 @@
}
}
PermissionTemplate permissionTemplate = new PermissionTemplate(templateName, permissionStrings);
permissionTemplates.add(permissionTemplate);
permissionTemplates.put(templateName, permissionTemplate);

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 1355 is not covered by tests
}

strategy.permissionTemplates = permissionTemplates;
Expand Down
Loading
Loading