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

Implement type safe configuration-as-code integration #111

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -32,6 +32,8 @@
import jenkins.model.IdStrategy;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.matrixauth.integrations.PermissionFinder;
import org.jenkinsci.plugins.matrixauth.integrations.casc.MatrixAuthorizationStrategyConfigurator;
import org.jenkinsci.plugins.matrixauth.integrations.casc.PermissionEntryForCasc;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

Expand Down Expand Up @@ -196,6 +198,13 @@ default void add(String shortForm) {
sid = shortForm.substring(firstEndIndex + 1);
LOGGER.log(Jenkins.get().getInitLevel().ordinal() < InitMilestone.COMPLETED.ordinal() ? Level.WARNING : Level.FINE, "Processing a permission assignment in the legacy format (without explicit TYPE prefix): " + shortForm);
}
Permission p = getPermission(shortForm, permissionString, sid);
if (p == null) return;
add(p, new PermissionEntry(type, sid));
}

@Restricted(NoExternalUse.class)
/* private when allowed */ default Permission getPermission(String shortForm, String permissionString, String sid) {
Permission p = Permission.fromId(permissionString);
if (p == null) {
// attempt to find the permission based on the 'nice' name, e.g. Overall/Administer
Expand All @@ -207,9 +216,21 @@ default void add(String shortForm) {
if (!p.isContainedBy(((AuthorizationContainerDescriptor<?>) getDescriptor()).getPermissionScope())) {
LOGGER.log(Level.WARNING,
"Tried to add inapplicable permission " + p + " for " + sid + " in " + this + ", skipping");
return;
return null;
}
add(p, new PermissionEntry(type, sid));
return p;
}

@Restricted(NoExternalUse.class)
default void add(PermissionEntryForCasc permissionEntryForCasc) {
PermissionEntry entry = permissionEntryForCasc.retrieveEntry();
Permission p = getPermission(
permissionEntryForCasc.getPermission(),
permissionEntryForCasc.getPermission(),
entry.getSid()
);

add(p, entry);
}

@Restricted(NoExternalUse.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ protected AuthorizationMatrixNodeProperty instance(Mapping mapping, Configuratio
@Nonnull
public Set<Attribute<AuthorizationMatrixNodeProperty, ?>> describe() {
return new HashSet<>(Arrays.asList(
new MultivaluedAttribute<AuthorizationMatrixNodeProperty, String>("permissions", String.class)
new MultivaluedAttribute<AuthorizationMatrixNodeProperty, PermissionEntryForCasc>("permissions", PermissionEntryForCasc.class)
.getter(MatrixAuthorizationStrategyConfigurator::getPermissions)
.setter(MatrixAuthorizationStrategyConfigurator::setPermissions),
new DescribableAttribute<AuthorizationMatrixNodeProperty, InheritanceStrategy>("inheritanceStrategy", InheritanceStrategy.class)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
import io.jenkins.plugins.casc.Attribute;
import io.jenkins.plugins.casc.BaseConfigurator;
import io.jenkins.plugins.casc.impl.attributes.MultivaluedAttribute;
import java.util.Collections;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.matrixauth.AuthorizationContainer;
import org.jenkinsci.plugins.matrixauth.AuthorizationContainerDescriptor;
import org.jenkinsci.plugins.matrixauth.AuthorizationType;
import org.jenkinsci.plugins.matrixauth.PermissionEntry;
import org.jenkinsci.plugins.matrixauth.integrations.PermissionFinder;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
Expand All @@ -19,6 +23,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.kohsuke.stapler.DataBoundConstructor;

@Restricted(NoExternalUse.class)
public abstract class MatrixAuthorizationStrategyConfigurator<T extends AuthorizationContainer> extends BaseConfigurator<T> {
Expand All @@ -29,47 +34,43 @@ public Class<?> getImplementedAPI() {
return AuthorizationStrategy.class;
}


@Override
@Nonnull
public Set<Attribute<T, ?>> describe() {
return new HashSet<>(Arrays.asList(
new MultivaluedAttribute<T, String>("permissions", String.class)
return new HashSet<>(Collections.singletonList(
new MultivaluedAttribute<T, PermissionEntryForCasc>("permissions", PermissionEntryForCasc.class)
.getter(MatrixAuthorizationStrategyConfigurator::getPermissions)
.setter(MatrixAuthorizationStrategyConfigurator::setPermissions),

// support old style configuration options
new MultivaluedAttribute<T, String>("grantedPermissions", String.class)
.getter(unused -> null)
.setter(MatrixAuthorizationStrategyConfigurator::setPermissionsDeprecated)
.setter(MatrixAuthorizationStrategyConfigurator::setPermissions)
));
}

/**
* Extract container's permissions as a List of "TYPE:PERMISSION:sid"
*/
public static Collection<String> getPermissions(AuthorizationContainer container) {
public static Collection<PermissionEntryForCasc> getPermissions(AuthorizationContainer container) {
return container.getGrantedPermissionEntries().entrySet().stream()
.flatMap( e -> e.getValue().stream()
.map(v -> v.getType().toPrefix() + e.getKey().group.getId() + "/" + e.getKey().name + ":" + v.getSid()))
.map(v -> {
PermissionEntryForCasc entry = new PermissionEntryForCasc(e.getKey().group.getId() + "/" + e.getKey().name);
if (v.getType().equals(AuthorizationType.USER)) {
entry.setUser(v.getSid());
return entry;
} else if (v.getType().equals(AuthorizationType.GROUP)) {
entry.setGroup(v.getSid());
return entry;
}
throw new IllegalStateException("Ambiguous users are not supported: " + v.getSid());
timja marked this conversation as resolved.
Show resolved Hide resolved
}))
.sorted()
.collect(Collectors.toList());
}

/**
* Configure container's permissions from a List of "PERMISSION:sid" or "TYPE:PERMISSION:sid"
*/
public static void setPermissions(AuthorizationContainer container, Collection<String> permissions) {
public static void setPermissions(AuthorizationContainer container, Collection<PermissionEntryForCasc> permissions) {
permissions.forEach(container::add);
}

/**
* Like {@link #setPermissions(AuthorizationContainer, Collection)} but logs a deprecation warning
*/
public static void setPermissionsDeprecated(AuthorizationContainer container, Collection<String> permissions) {
LOGGER.log(Level.WARNING, "Loading deprecated attribute 'grantedPermissions' for instance of '" + container.getClass().getName() +"'. Use 'permissions' instead.");
setPermissions(container, permissions);
}

private static final Logger LOGGER = Logger.getLogger(MatrixAuthorizationStrategyConfigurator.class.getName());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.jenkinsci.plugins.matrixauth.integrations.casc;

import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.matrixauth.PermissionEntry;
import org.kohsuke.stapler.DataBoundConstructor;

public class PermissionEntryForCasc implements Comparable<PermissionEntryForCasc> {
private String user;
private String group;
private String permission;

@DataBoundConstructor
public PermissionEntryForCasc(String permission) {
this.permission = permission;
}

public String getPermission() {
return permission;
}

public String getUser() {
return user;
}

public void setUser(String user) {
this.user = user;
}

public String getGroup() {
return group;
}

public void setGroup(String group) {
this.group = group;
}

public PermissionEntry retrieveEntry() {
if (StringUtils.isNotBlank(user)) {
return PermissionEntry.user(user);
}

if (StringUtils.isNotBlank(group)) {
return PermissionEntry.group(group);
}
throw new IllegalStateException("One of 'group' or 'user' must be set, permission was: " + permission);
}

@Override
public int compareTo(PermissionEntryForCasc obj) {
return permission.compareTo(obj.permission);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PermissionEntryForCasc that = (PermissionEntryForCasc) o;
return Objects.equals(user, that.user) && Objects.equals(group, that.group) && permission.equals(that.permission);
}

@Override
public int hashCode() {
return Objects.hash(user, group, permission);
}
}