forked from synopsys-arc-oss/ownership-plugin
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[JENKINS-28881] - Add unit tests for Ownership-based security
- Loading branch information
1 parent
1e3e70f
commit 535baec
Showing
2 changed files
with
349 additions
and
0 deletions.
There are no files selected for viewing
131 changes: 131 additions & 0 deletions
131
...ava/org/jenkinsci/plugins/ownership/security/rolestrategy/OwnershipBasedSecurityTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
}); | ||
} | ||
} |
218 changes: 218 additions & 0 deletions
218
...g/jenkinsci/plugins/ownership/security/rolestrategy/OwnershipBasedSecurityTestHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |