Merge pull request #94 from jenkinsci/16-global-matrix-auth
[WiP] First draft for Global Matrix Authorization
This commit is contained in:
commit
a047aeaf5c
|
|
@ -0,0 +1,12 @@
|
|||
jenkins:
|
||||
authorizationStrategy:
|
||||
globalMatrix:
|
||||
grantedPermissions:
|
||||
- group:
|
||||
name: "anonymous"
|
||||
permissions:
|
||||
- "hudson.model.Hudson.Read"
|
||||
- group
|
||||
name: "authenticated"
|
||||
permissions:
|
||||
- "hudson.model.Hudson.Administer"
|
||||
6
pom.xml
6
pom.xml
|
|
@ -92,6 +92,12 @@
|
|||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>matrix-auth</artifactId>
|
||||
<version>2.2</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- tested plugins -->
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,5 @@ public class HudsonPrivateSecurityRealmConfigurator extends DataBoundConfigurato
|
|||
this.password = password;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package org.jenkinsci.plugins.casc.integrations.globalmatrixauth;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import hudson.Extension;
|
||||
import hudson.security.GlobalMatrixAuthorizationStrategy;
|
||||
import hudson.security.Permission;
|
||||
import org.jenkinsci.plugins.casc.Attribute;
|
||||
import org.jenkinsci.plugins.casc.Configurator;
|
||||
import org.jenkinsci.plugins.casc.RootElementConfigurator;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Mads Nielsen
|
||||
* @since TODO
|
||||
*/
|
||||
@Extension(optional = true)
|
||||
@Restricted(NoExternalUse.class)
|
||||
public class GlobalMatrixAuthorizationStrategyConfigurator extends Configurator<GlobalMatrixAuthorizationStrategy> implements RootElementConfigurator {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "globalMatrix";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<GlobalMatrixAuthorizationStrategy> getTarget() {
|
||||
return GlobalMatrixAuthorizationStrategy.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressFBWarnings(value = "DM_NEW_FOR_GETCLASS", justification = "We need a fully qualified type to do proper attribute binding")
|
||||
public Set<Attribute> describe() {
|
||||
return Collections.singleton(new Attribute<GroupPermissionDefinition>("grantedPermissions", new HashSet<GroupPermissionDefinition>().getClass()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalMatrixAuthorizationStrategy configure(Object config) throws Exception {
|
||||
Map map = (Map) config;
|
||||
Collection o = (Collection<?>)map.get("grantedPermissions");
|
||||
Configurator<GroupPermissionDefinition> permissionConfigurator = Configurator.lookup(GroupPermissionDefinition.class);
|
||||
Map<Permission,Set<String>> grantedPermissions = new HashMap<>();
|
||||
for(Object entry : o) {
|
||||
GroupPermissionDefinition gpd = permissionConfigurator.configure(entry);
|
||||
//We transform the linear list to a matrix (Where permission is the key instead)
|
||||
gpd.grantPermission(grantedPermissions);
|
||||
}
|
||||
|
||||
//TODO: Once change is in place for GlobalMatrixAuthentication. Switch away from reflection
|
||||
GlobalMatrixAuthorizationStrategy gms = new GlobalMatrixAuthorizationStrategy();
|
||||
Field f = gms.getClass().getDeclaredField("grantedPermissions");
|
||||
f.setAccessible(true);
|
||||
f.set(gms, grantedPermissions);
|
||||
return gms;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package org.jenkinsci.plugins.casc.integrations.globalmatrixauth;
|
||||
|
||||
import hudson.security.Permission;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
import org.kohsuke.stapler.DataBoundConstructor;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author Mads Nielsen
|
||||
* @since TODO
|
||||
*/
|
||||
@Restricted(NoExternalUse.class)
|
||||
public class GroupPermissionDefinition {
|
||||
|
||||
private String name;
|
||||
private Collection<String> permissions;
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(GroupPermissionDefinition.class.getName());
|
||||
|
||||
@DataBoundConstructor
|
||||
public GroupPermissionDefinition(String name, Collection<String> permissions) {
|
||||
this.name = name;
|
||||
this.permissions = permissions != null ? Collections.unmodifiableCollection(permissions) : Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
public void grantPermission(Map<Permission,Set<String>> grantedPermissions) {
|
||||
|
||||
for(String permission : permissions) {
|
||||
//Permission pm = Permission.fromId(permission);
|
||||
Permission pm = PermissionFinder.findPermission(permission);
|
||||
if(pm != null) {
|
||||
if (grantedPermissions.containsKey(pm)) {
|
||||
grantedPermissions.get(pm).add(name);
|
||||
} else {
|
||||
HashSet<String> s = new HashSet<>();
|
||||
s.add(name);
|
||||
grantedPermissions.put(pm, s);
|
||||
}
|
||||
} else {
|
||||
LOGGER.warning(String.format("Ignoring unknown permission with id: '%s'", permission));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("'%s' granted [%s]", name, permissions != null ? String.join(",", permissions) : "" );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package org.jenkinsci.plugins.casc.integrations.globalmatrixauth;
|
||||
|
||||
import hudson.security.Permission;
|
||||
import hudson.security.PermissionGroup;
|
||||
import jenkins.model.Jenkins;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Created by mads on 2/9/18.
|
||||
*/
|
||||
public class PermissionFinder {
|
||||
|
||||
/** For Matrix Auth - Title/Permission **/
|
||||
private static final Pattern PERMISSION_PATTERN = Pattern.compile("(\\w+)/(\\w+)");
|
||||
|
||||
/**
|
||||
* Attempt to match a given permission to what is defined in the UI.
|
||||
* TODO: Refector this away when proper permission API is in place for C-as-C
|
||||
* @param id String of the form "Title/Permission" (Look in the UI) for a particular permission
|
||||
* @return a matched permission
|
||||
*/
|
||||
@CheckForNull
|
||||
public static Permission findPermission(String id) {
|
||||
List<PermissionGroup> pgs = PermissionGroup.getAll();
|
||||
Matcher m = PERMISSION_PATTERN.matcher(id);
|
||||
if(m.matches()) {
|
||||
String owner = m.group(1);
|
||||
String name = m.group(2);
|
||||
for(PermissionGroup pg : pgs) {
|
||||
if(pg.owner.equals(Permission.class)) {
|
||||
continue;
|
||||
}
|
||||
//How do we do this properly, we want to mimic the UI as best as possible. So the logic conclusion is
|
||||
//That when you want admin to be Overall/Administer you put that in. Overall being the group title...
|
||||
//Name being the Permssion you want to set in the matrix.
|
||||
if(pg.title.toString().equals(owner)) {
|
||||
return Permission.fromId(pg.owner.getName()+"."+name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package org.jenkinsci.plugins.casc.integrations;
|
||||
package org.jenkinsci.plugins.casc.integrations.rolebasedauth;
|
||||
|
||||
|
||||
import com.cloudbees.plugins.credentials.domains.Domain;
|
||||
import com.michelin.cio.hudson.plugins.rolestrategy.Role;
|
||||
import com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy;
|
||||
import com.michelin.cio.hudson.plugins.rolestrategy.RoleMap;
|
||||
|
|
@ -21,7 +20,6 @@ import java.util.HashMap;
|
|||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.jenkinsci.plugins.casc.integrations;
|
||||
package org.jenkinsci.plugins.casc.integrations.rolebasedauth;
|
||||
|
||||
import com.michelin.cio.hudson.plugins.rolestrategy.Role;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package org.jenkinsci.plugins.casc.integrations.globalmatrixauth;
|
||||
|
||||
import hudson.security.AuthorizationStrategy;
|
||||
import hudson.security.GlobalMatrixAuthorizationStrategy;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.jenkinsci.plugins.casc.ConfigurationAsCode;
|
||||
import org.jenkinsci.plugins.casc.Configurator;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* @author Mads Nielsen
|
||||
* @since TODO
|
||||
*/
|
||||
public class GlobalMatrixAuthorizationTest {
|
||||
|
||||
@ClassRule
|
||||
public static JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Test
|
||||
public void shouldReturnCustomConfigurator() {
|
||||
Configurator configurator = Configurator.lookup(GlobalMatrixAuthorizationStrategy.class);
|
||||
assertNotNull("Failed to find configurator for GlobalMatrixAuthorizationStrategy", configurator);
|
||||
assertEquals("Retrieved wrong configurator", GlobalMatrixAuthorizationStrategyConfigurator.class, configurator.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnCustomConfiguratorForBaseType() {
|
||||
Configurator c = Configurator.lookupForBaseType(AuthorizationStrategy.class, "globalMatrix");
|
||||
assertNotNull("Failed to find configurator for GlobalMatrixAuthorizationStrategy", c);
|
||||
assertEquals("Retrieved wrong configurator", GlobalMatrixAuthorizationStrategyConfigurator.class, c.getClass());
|
||||
Configurator.lookup(GlobalMatrixAuthorizationStrategy.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkCorrectlyConfiguredPermissions() throws Exception {
|
||||
ConfigurationAsCode.configure(getClass().getResourceAsStream("GlobalMatrixStrategy.yml"));
|
||||
assertEquals("The configured instance must use the Global Matrix Authentication Strategy", GlobalMatrixAuthorizationStrategy.class, Jenkins.getInstance().getAuthorizationStrategy().getClass());
|
||||
GlobalMatrixAuthorizationStrategy gms = (GlobalMatrixAuthorizationStrategy)Jenkins.getInstance().getAuthorizationStrategy();
|
||||
|
||||
List<String> adminPermission = new ArrayList<>(gms.getGrantedPermissions().get(Jenkins.ADMINISTER));
|
||||
assertEquals("authenticated", adminPermission.get(0));
|
||||
|
||||
List<String> readPermission = new ArrayList<>(gms.getGrantedPermissions().get(Jenkins.READ));
|
||||
assertEquals("anonymous", readPermission.get(0));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
package org.jenkinsci.plugins.casc.integrations;
|
||||
package org.jenkinsci.plugins.casc.integrations.rolebasedauth;
|
||||
|
||||
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 hudson.security.AuthorizationStrategy;
|
||||
import hudson.security.Permission;
|
||||
import jenkins.model.Jenkins;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import org.jenkinsci.plugins.casc.ConfigurationAsCode;
|
||||
import org.jenkinsci.plugins.casc.Configurator;
|
||||
import org.jenkinsci.plugins.casc.integrations.rolebasedauth.RoleBasedAuthorizationStrategyConfigurator;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.Issue;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
|
|
@ -50,9 +50,10 @@ public class RoleStrategyTest {
|
|||
@Test
|
||||
@Issue("Issue #48")
|
||||
public void shouldReadRolesCorrectly() throws Exception {
|
||||
ConfigurationAsCode.configure(getClass().getResourceAsStream("role-strategy/RoleStrategy1.yml"));
|
||||
ConfigurationAsCode.configure(getClass().getResourceAsStream("RoleStrategy1.yml"));
|
||||
|
||||
final Jenkins jenkins = Jenkins.getInstance();
|
||||
|
||||
AuthorizationStrategy s = jenkins.getAuthorizationStrategy();
|
||||
assertThat("Authorization Strategy has been read incorrectly",
|
||||
s, instanceOf(RoleBasedAuthorizationStrategy.class));
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
jenkins:
|
||||
systemMessage: "Welcome!"
|
||||
numExecutors: 4
|
||||
scmCheckoutRetryCount: 2
|
||||
mode: NORMAL
|
||||
scmCheckoutRetryCount: 4
|
||||
securityRealm:
|
||||
local:
|
||||
allowsSignup: false
|
||||
users:
|
||||
- id: test
|
||||
password: test
|
||||
|
||||
authorizationStrategy:
|
||||
globalMatrix:
|
||||
grantedPermissions:
|
||||
- name: "anonymous"
|
||||
permissions:
|
||||
- "Overall/Read"
|
||||
- name: "authenticated"
|
||||
permissions:
|
||||
- "Overall/Administer"
|
||||
|
||||
Loading…
Reference in New Issue