Skip to content

Commit

Permalink
SECURITY-2254
Browse files Browse the repository at this point in the history
  • Loading branch information
MRamonLeon committed Apr 15, 2021
1 parent b7f3c51 commit d615e32
Show file tree
Hide file tree
Showing 7 changed files with 429 additions and 8 deletions.
@@ -1,10 +1,13 @@
package org.jenkinsci.plugins.configfiles.maven.security;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import hudson.model.*;
import hudson.security.Permission;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
Expand Down Expand Up @@ -48,12 +51,16 @@ public Descriptor<ServerCredentialMapping> getDescriptor() {
@Extension
public static class DescriptorImpl extends Descriptor<ServerCredentialMapping> {

public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context, @QueryParameter String serverId) {
AccessControlled _context = (context instanceof AccessControlled ? (AccessControlled) context : Jenkins.get());
if (_context == null || !_context.hasPermission(Item.CONFIGURE)) {
public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context, @AncestorInPath Item projectOrFolder, @QueryParameter String serverId) {
List<Permission> permsToCheck = projectOrFolder == null ? Arrays.asList(Jenkins.ADMINISTER) : Arrays.asList(Item.EXTENDED_READ, CredentialsProvider.USE_ITEM);
AccessControlled contextToCheck = projectOrFolder == null ? Jenkins.get() : projectOrFolder;

// If we're on the global page and we don't have administer permission or if we're in a project or folder
// and we don't have permission to use credentials and extended read in the item
if (permsToCheck.stream().anyMatch( per -> !contextToCheck.hasPermission(per))) {
return new StandardUsernameListBoxModel().includeCurrentValue(serverId);
}

List<DomainRequirement> domainRequirements = Collections.emptyList();
if (StringUtils.isNotBlank(serverId)) {
domainRequirements = Collections.singletonList(new MavenServerIdRequirement(serverId));
Expand Down
Expand Up @@ -11,6 +11,7 @@
import hudson.model.Queue;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import hudson.util.ListBoxModel;
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
Expand Down Expand Up @@ -50,12 +51,16 @@ public Descriptor<PropertiesCredentialMapping> getDescriptor() {
@Extension
public static class DescriptorImpl extends Descriptor<PropertiesCredentialMapping> {

public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context, @QueryParameter String propertyKey) {
AccessControlled _context = (context instanceof AccessControlled ? (AccessControlled) context : Jenkins.get());
if (_context == null || !_context.hasPermission(Item.CONFIGURE)) {
public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context, @AncestorInPath Item projectOrFolder, @QueryParameter String propertyKey) {
Permission permToCheck = projectOrFolder == null ? Jenkins.ADMINISTER : Item.CONFIGURE;
AccessControlled contextToCheck = projectOrFolder == null ? Jenkins.get() : projectOrFolder;

// If we're on the global page and we don't have administer permission or if we're in a project or folder
// and we don't have configure permission there
if (!contextToCheck.hasPermission(permToCheck)) {
return new StandardUsernameListBoxModel().includeCurrentValue(propertyKey);
}

List<DomainRequirement> domainRequirements = Collections.emptyList();
if (StringUtils.isNotBlank(propertyKey)) {
domainRequirements = Collections.singletonList(new PropertyKeyRequirement(propertyKey));
Expand Down
@@ -0,0 +1,71 @@
package org.jenkinsci.plugins.configfiles.sec;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.security.AccessDeniedException2;
import hudson.security.Permission;
import org.hamcrest.core.IsEqual;

import java.util.function.Supplier;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;

/**
* A class to run pieces of code with a certain user and assert either the code runs successfully, without even worrying
* about the result, or the code fails with an {@link AccessDeniedException2} with the specified {@link Permission}.
*/
public class PermissionChecker extends ProtectedCodeRunner<Void> {
/**
* Build a checker to run a specific code with this user and assert it runs successfully or it fails because certain
* permission.
* @param code The code to execute.
* @param user The user to execute with.
*/
public PermissionChecker(@NonNull Runnable code, @NonNull String user) {
super(getSupplier(code), user);
}

/**
* Assert the execution of the code by this user fails with this permission. The code throws an {@link AccessDeniedException2}
* with the permission field being permission. Otherwise it fails.
* @param permission The permission thrown by the code.
*/
public void assertFailWithPermission(Permission permission) {
Throwable t = getThrowable();
if (t instanceof AccessDeniedException2) {
assertThat(((AccessDeniedException2) t).permission, IsEqual.equalTo(permission));
} else {
fail(String.format("The code run by %s didn't throw an AccessDeniedException2 with %s. If failed with the unexpected throwable: %s", getUser(), permission, t));
}
}

/**
* Assert the execution is done without any exception. The result doesn't matter.
*/
public void assertPass() {
getResult(); // The result doesn't matter
}

/**
* Change the user to run the code with.
* @param user The user.
* @return This object.
*/
@Override
public PermissionChecker withUser(String user) {
super.withUser(user);
return this;
}

/**
* Get a supplier from a runnable.
* @param code The runnable to run.
* @return A supplier executing the runnable code and returning just null.
*/
private static Supplier<Void> getSupplier(Runnable code) {
return () -> {
code.run();
return null;
};
}
}
@@ -0,0 +1,34 @@
package org.jenkinsci.plugins.configfiles.sec;

import jenkins.model.Jenkins;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;

public class PermissionCheckerTests {
@Rule
public JenkinsRule r = new JenkinsRule();

@Before
public void setUpAuthorizationAndProject() {
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().
grant(Jenkins.READ).everywhere().to("reader").
grant(Jenkins.ADMINISTER).everywhere().to("administer")
);
}

@Test
public void protectedCodeCheckerTest() {
Runnable run = () -> r.jenkins.checkPermission(Jenkins.ADMINISTER);

// The administer passes
PermissionChecker checker = new PermissionChecker(run, "administer");
checker.assertPass();

// The reader fails
checker.withUser("reader").assertFailWithPermission(Jenkins.ADMINISTER);
}
}
@@ -0,0 +1,79 @@
package org.jenkinsci.plugins.configfiles.sec;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.User;
import hudson.security.ACL;
import hudson.security.ACLContext;

import java.util.function.Supplier;

import static org.junit.Assert.fail;

/**
* Class to run a code returning something with a specific user. You can get the result or the {@link Throwable} thrown
* by the code executed. Afterwards you can assert the result or the exception thrown.
* @param <Result>
*/
public class ProtectedCodeRunner<Result> {
@NonNull
private final Supplier<Result> code;
@NonNull
private String user;

/**
* Create an object to run this piece of code with this Jenkins user.
* @param code The code to be executed.
* @param user The user executing this code.
*/
public ProtectedCodeRunner(@NonNull Supplier<Result> code, @NonNull String user) {
this.code = code;
this.user = user;
}

/**
* We run the code expecting to get a result. If the execution throws an exception (Throwable), the test fails with
* a descriptive message.
* @return The result of the execution.
*/
public Result getResult() {
try (ACLContext ctx = ACL.as(User.getOrCreateByIdOrFullName(user))) {
return code.get();
} catch (Throwable t) {
fail(String.format("The code executed by %s didn't run successfully. The throwable thrown is: %s", user, t));
return null;
}
}

/**
* We run the code expecting an exception to be thrown. If it's not the case, the test fails with a descriptive
* message.
* @return The {@link Throwable} thrown by the execution.
*/
public Throwable getThrowable() {
try (ACLContext ctx = ACL.as(User.getOrCreateByIdOrFullName(user))) {
Result result = code.get();
fail(String.format("The code executed by %s was successful but we were expecting it to fail. The result of the execution was: %s", user, result));
return null;
} catch (Throwable t) {
return t;
}
}

/**
* Get the user.
* @return The user.
*/
public String getUser() {
return user;
}

/**
* Use a different user.
* @param user The user.
* @return This object.
*/
public ProtectedCodeRunner<Result> withUser(String user) {
this.user = user;
return this;
}
}
@@ -0,0 +1,48 @@
package org.jenkinsci.plugins.configfiles.sec;

import hudson.security.AccessDeniedException2;
import jenkins.model.Jenkins;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;

import java.util.function.Supplier;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;

/**
* Check the {@link ProtectedCodeRunner} class works correctly.
*/
public class ProtectedCodeRunnerTests {
@Rule
public JenkinsRule r = new JenkinsRule();

@Before
public void setUpAuthorizationAndProject() {
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().
grant(Jenkins.READ).everywhere().to("reader").
grant(Jenkins.ADMINISTER).everywhere().to("administer")
);
}

@Test
public void protectedCodeCheckerTest() {
Supplier<String> supplier = () -> {
r.jenkins.checkPermission(Jenkins.ADMINISTER);
return "allowed";
};

ProtectedCodeRunner<String> checker = new ProtectedCodeRunner<>(supplier, "administer");
assertThat(checker.getResult(), is("allowed"));

Throwable t = checker.withUser("reader").getThrowable();
assertThat(t, instanceOf(AccessDeniedException2.class));
assertThat(((AccessDeniedException2) t).permission, equalTo(Jenkins.ADMINISTER));
}
}

0 comments on commit d615e32

Please sign in to comment.