Skip to content
Permalink
Browse files
[JENKINS-28881] - Add unit tests for Ownership-based security
  • Loading branch information
oleg-nenashev committed Jan 6, 2016
1 parent 1e3e70f commit 535baec000a54decbd214ba6a3206f28f4736e34
Showing 2 changed files with 349 additions and 0 deletions.
@@ -0,0 +1,131 @@
/*
* The MIT License
*
* Copyright (c) 2016 Oleg Nenashev.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins.ownership.security.rolestrategy;

import com.cloudbees.hudson.plugins.folder.Folder;
import com.synopsys.arc.jenkins.plugins.ownership.OwnershipDescription;
import com.synopsys.arc.jenkins.plugins.ownership.jobs.JobOwnerHelper;
import hudson.model.FreeStyleProject;
import hudson.model.Item;
import hudson.model.User;
import hudson.remoting.Callable;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import java.util.Arrays;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import org.jenkinsci.plugins.ownership.folders.FolderOwnershipHelper;
import org.jenkinsci.remoting.RoleChecker;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;

/**
* Checks ownership-based security.
* @author Oleg Nenashev
*/
public class OwnershipBasedSecurityTest {

@Rule
public final JenkinsRule j = new JenkinsRule();

@Test
public void shouldWorkForProjects() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
OwnershipBasedSecurityTestHelper.setup(j.jenkins);

FreeStyleProject project = j.createFreeStyleProject("project");
JobOwnerHelper.setOwnership(project, new OwnershipDescription(true, "owner", Arrays.asList("coOwner")));

verifyItemPermissions(project);
}

@Test
@Issue("JENKINS-28881")
public void shouldWorkForProjectsWithInheritedOwnership() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
OwnershipBasedSecurityTestHelper.setup(j.jenkins);

Folder folder = j.jenkins.createProject(Folder.class, "folder");
FreeStyleProject project = folder.createProject(FreeStyleProject.class, "project");
FolderOwnershipHelper.setOwnership(folder, new OwnershipDescription(true, "owner", Arrays.asList("coOwner")));

// Verify that permissions are inherited by project
verifyItemPermissions(project);

// Also check folder permissions
verifyItemPermissions(folder);
}

private void verifyItemPermissions(Item item) {
// Admin
assertHasPermission("admin", item, Item.READ);
assertHasPermission("admin", item, Item.DELETE);
assertHasPermission("admin", item, Item.CONFIGURE);

// Owner
assertHasPermission("owner", item, Item.READ);
assertHasPermission("owner", item, Item.DELETE);
assertHasPermission("owner", item, Item.CONFIGURE);

// CoOwner
assertHasPermission("coOwner", item, Item.READ);
assertHasNoPermission("coOwner", item, Item.DELETE);
assertHasPermission("coOwner", item, Item.CONFIGURE);

// User
assertHasPermission("user", item, Item.READ);
assertHasNoPermission("user", item, Item.DELETE);
assertHasNoPermission("user", item, Item.CONFIGURE);
}

private void assertHasPermission(String userId, final AccessControlled item, final Permission p) {
assertThat("User '" + userId + "' has no " + p.getId() + " permission for " + item + ", but it should according to security settings",
hasPermission(userId, item, p), equalTo(true));
}

private void assertHasNoPermission(String userId, final AccessControlled item, final Permission p) {
assertThat("User '" + userId + "' has the " + p.getId() + " permission for " + item + ", but it should not according to security settings",
hasPermission(userId, item, p), equalTo(false));
}

private boolean hasPermission(String userId, final AccessControlled item, final Permission p) {
User user = User.get(userId);
return ACL.impersonate(user.impersonate(), new Callable<Boolean, IllegalStateException>() {
private static final long serialVersionUID = 1L;

@Override
public Boolean call() throws IllegalStateException {
return item.hasPermission(p);
}

@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
// Do nothing
}
});
}
}
@@ -0,0 +1,218 @@
/*
* The MIT License
*
* Copyright (c) 2016 Oleg Nenashev.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins.ownership.security.rolestrategy;

import com.michelin.cio.hudson.plugins.rolestrategy.Role;
import com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy;
import com.michelin.cio.hudson.plugins.rolestrategy.RoleMap;
import com.synopsys.arc.jenkins.plugins.ownership.OwnershipPlugin;
import com.synopsys.arc.jenkins.plugins.rolestrategy.RoleType;
import hudson.model.Computer;
import hudson.model.Item;
import hudson.model.Run;
import hudson.security.Permission;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;

/**
* Provides functions for Ownership-based security setup.
* @author Oleg Nenashev
*/
public class OwnershipBasedSecurityTestHelper {

public static void setup(@Nonnull Jenkins jenkins) throws AssertionError, IOException {

RoleBasedAuthorizationStrategy strategy = new RoleBasedAuthorizationStrategy();

Map<String,RoleMap> grantedRoles = new HashMap<String, RoleMap>();
grantedRoles.put(RoleType.Project.getStringType(), getProjectRoleMap());
grantedRoles.put(RoleType.Slave.getStringType(), getComputerRoleMap());
grantedRoles.put(RoleType.Global.getStringType(), getGlobalAdminAndAnonymousRoles());

setGrantedRoles(strategy, grantedRoles);
jenkins.setAuthorizationStrategy(strategy);
jenkins.save();
}

private static RoleMap getGlobalAdminAndAnonymousRoles() {
Set<Permission> adminPermissions = new HashSet<Permission>();
adminPermissions.add(Jenkins.ADMINISTER);
Role adminRole = createRole("administrator", ".*", adminPermissions);

Set<Permission> anonymousPermissions = new HashSet<Permission>();
anonymousPermissions.add(Jenkins.READ);
anonymousPermissions.add(Item.READ);
anonymousPermissions.add(Item.DISCOVER);
Role anonymousRole = createRole("anonymous", ".*", anonymousPermissions);

final SortedMap<Role,Set<String>> grantedRoles = new TreeMap<Role, Set<String>>();
grantedRoles.put(adminRole, singleSid("admin"));
grantedRoles.put(anonymousRole, singleSid("anonymous"));

return createRoleMap(grantedRoles);
}

private static RoleMap getProjectRoleMap() {
Set<Permission> ownerPermissions = new HashSet<Permission>();
ownerPermissions.add(OwnershipPlugin.MANAGE_ITEMS_OWNERSHIP);
ownerPermissions.addAll(Item.PERMISSIONS.getPermissions());
ownerPermissions.addAll(Run.PERMISSIONS.getPermissions());
Role ownerRole = createRole("@OwnerNoSid", ".*", ownerPermissions);

Set<Permission> coownerPermissions = new HashSet<Permission>();
coownerPermissions.addAll(Item.PERMISSIONS.getPermissions());
coownerPermissions.addAll(Run.PERMISSIONS.getPermissions());
coownerPermissions.remove(Item.DELETE);
coownerPermissions.remove(Run.DELETE);
Role coOwnerRole = createRole("@CoOwnerNoSid", ".*", coownerPermissions);

return createRoleMapForSid("authenticated", ownerRole, coOwnerRole);
}

private static RoleMap getComputerRoleMap() {
Set<Permission> ownerPermissions = new HashSet<Permission>();
ownerPermissions.add(OwnershipPlugin.MANAGE_SLAVES_OWNERSHIP);
ownerPermissions.addAll(Computer.PERMISSIONS.getPermissions());
Role ownerRole = createRole("@OwnerNoSid", ".*", ownerPermissions);

Set<Permission> coownerPermissions = new HashSet<Permission>();
coownerPermissions.addAll(Computer.PERMISSIONS.getPermissions());
coownerPermissions.remove(Computer.DELETE);
coownerPermissions.remove(Computer.CONFIGURE);
Role coOwnerRole = createRole("@CoOwnerNoSid", ".*", coownerPermissions);

return createRoleMapForSid("authenticated", ownerRole, coOwnerRole);
}

private static Role createRole(String name, String pattern, Permission ... permissions) {
Set<Permission> permSet = new HashSet<Permission>();
for (Permission p : permissions) {
permSet.add(p);
}
return createRole(name, pattern, permSet);
}

private static RoleMap createRoleMapForSid(String sid, Role ... roles) {
final SortedMap<Role,Set<String>> grantedRoles = new TreeMap<Role, Set<String>>();
for (Role role : roles) {
grantedRoles.put(role, singleSid(sid));
}
return createRoleMap(grantedRoles);
}

private static Set<String> singleSid(String sid) {
final Set<String> sids = new TreeSet<String>();
sids.add(sid);
return sids;
}

// TODO: Methods below should be burned with fire when RoleStrategy gets better API
private static Role createRole(String name, String pattern, Set<Permission> permissions) {
try {
Constructor<Role> constructor = locateConstructor(Role.class, String.class, String.class, Set.class);
try {
constructor.setAccessible(true);
return constructor.newInstance(name, pattern, permissions);
} finally {
constructor.setAccessible(false);
}
} catch (Exception ex) {
throw new AssertionError("Cannot create role", ex);
}
}

@Nonnull
@SuppressWarnings("unchecked")
private static <T> Constructor<T> locateConstructor(Class<T> itemClass, Class<?> ... parameters) {
Constructor<?>[] constructors = itemClass.getDeclaredConstructors();
for (Constructor<?> c : constructors) {
Class<?>[] parameterTypes = c.getParameterTypes();
if (parameterTypes.length != parameters.length) {
continue;
}

boolean matches = true;
for (int i = 0; i < parameters.length; ++i) {
if (!parameterTypes[i].isAssignableFrom(parameters[i])) {
matches = false;
break;
}
}

if (matches) {
return (Constructor<T>)c;
}
}

throw new IllegalStateException("Cannot locate a constructor for " + itemClass);
}

private static RoleMap createRoleMap(SortedMap<Role,Set<String>> grantedRoles) {
try {
Constructor<RoleMap> constructor = locateConstructor(RoleMap.class, SortedMap.class);
try {
constructor.setAccessible(true);
return constructor.newInstance(grantedRoles);
} finally {
constructor.setAccessible(false);
}
} catch (Exception ex) {
throw new AssertionError("Cannot create role map", ex);
}
}

private static void setGrantedRoles(@Nonnull RoleBasedAuthorizationStrategy strategy,
@Nonnull Map<String,RoleMap> grantedRoles) throws AssertionError {
final Field field;
try {
field = RoleBasedAuthorizationStrategy.class.getDeclaredField("grantedRoles");

try {
field.setAccessible(true);
@SuppressWarnings("unchecked")
Map<String,RoleMap> value = (Map<String,RoleMap>)field.get(strategy);
value.putAll(grantedRoles);
} finally {
field.setAccessible(false);
}
} catch (NoSuchFieldException ex) {
throw new AssertionError("Cannot modify roles", ex);
} catch (SecurityException ex) {
throw new AssertionError("Cannot modify roles", ex);
} catch (IllegalAccessException ex) {
throw new AssertionError("Cannot modify roles", ex);
}
}
}

0 comments on commit 535baec

Please sign in to comment.