Skip to content

Commit

Permalink
[WFCORE-4750] Using regex for role in Elytron
Browse files Browse the repository at this point in the history
  • Loading branch information
Skyllarr committed Nov 19, 2019
1 parent 0040c45 commit 7799670
Show file tree
Hide file tree
Showing 17 changed files with 242 additions and 4 deletions.
Expand Up @@ -256,6 +256,7 @@ public void registerChildren(ManagementResourceRegistration resourceRegistration
resourceRegistration.registerSubModel(new CustomComponentDefinition<>(RoleMapper.class, Function.identity(), ElytronDescriptionConstants.CUSTOM_ROLE_MAPPER, ROLE_MAPPER_RUNTIME_CAPABILITY));
resourceRegistration.registerSubModel(RoleMapperDefinitions.getLogicalRoleMapperDefinition());
resourceRegistration.registerSubModel(RoleMapperDefinitions.getMappedRoleMapperDefinition());
resourceRegistration.registerSubModel(RoleMapperDefinitions.getRegexRoleMapperDefinition());

// Evidence Decoders
resourceRegistration.registerSubModel(EvidenceDecoderDefinitions.getX500SubjectEvidenceDecoderDefinition());
Expand Down
Expand Up @@ -427,7 +427,9 @@ interface ElytronDescriptionConstants {
String REFERENCE = "reference";
String REFERRAL_MODE = "referral-mode";
String REGISTER_JASPI_FACTORY = "register-jaspi-factory";
String REGEX = "regex";
String REGEX_PRINCIPAL_TRANSFORMER = "regex-principal-transformer";
String REGEX_ROLE_MAPPER = "regex-role-mapper";
String REGEX_VALIDATING_PRINCIPAL_TRANSFORMER = "regex-validating-principal-transformer";
String RELATIVE_TO = "relative-to";
String REMOVE_ALIAS = "remove-alias";
Expand Down
Expand Up @@ -108,7 +108,7 @@ public void registerTransformers(SubsystemTransformerRegistration registration)

private static void from9(ChainedTransformationDescriptionBuilder chainedBuilder) {
ResourceTransformationDescriptionBuilder builder = chainedBuilder.createBuilder(ELYTRON_9_0_0, ELYTRON_8_0_0);

builder.rejectChildResource(PathElement.pathElement(ElytronDescriptionConstants.REGEX_ROLE_MAPPER));
}

private static void from8(ChainedTransformationDescriptionBuilder chainedBuilder) {
Expand Down
Expand Up @@ -205,6 +205,11 @@ public void marshallSingleElement(AttributeDefinition attribute, ModelNode prope
})
.build();

private PersistentResourceXMLDescription regexRoleMapperParser = PersistentResourceXMLDescription.builder(RoleMapperDefinitions.getRegexRoleMapperDefinition().getPathElement())
.addAttribute(RoleMapperDefinitions.REGEX)
.addAttribute(RoleMapperDefinitions.REPLACEMENT)
.build();

private PersistentResourceXMLDescription addPrefixRoleMapperParser = PersistentResourceXMLDescription.builder(RoleMapperDefinitions.getAddPrefixRoleMapperDefinition().getPathElement())
.addAttribute(RoleMapperDefinitions.PREFIX)
.build();
Expand Down Expand Up @@ -380,6 +385,7 @@ public PersistentResourceXMLDescription getParser() {
.addChild(x509SubjectAltNameEvidenceDecoder) // new
.addChild(getCustomComponentParser(CUSTOM_EVIDENCE_DECODER)) // new
.addChild(aggregateEvidenceDecoderParser) // new
.addChild(regexRoleMapperParser) // new
.build();
}
}
Expand Up @@ -20,6 +20,7 @@
import static org.wildfly.extension.elytron.Capabilities.ROLE_MAPPER_CAPABILITY;
import static org.wildfly.extension.elytron.Capabilities.ROLE_MAPPER_RUNTIME_CAPABILITY;
import static org.wildfly.extension.elytron.ElytronDefinition.commonDependencies;
import static org.wildfly.extension.elytron._private.ElytronSubsystemMessages.ROOT_LOGGER;

import java.util.HashSet;
import java.util.LinkedHashMap;
Expand All @@ -29,6 +30,8 @@
import java.util.Map;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AbstractWriteAttributeHandler;
Expand All @@ -47,6 +50,7 @@
import org.jboss.as.controller.capability.RuntimeCapability;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.operations.validation.EnumValidator;
import org.jboss.as.controller.operations.validation.ParameterValidator;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.OperationEntry;
import org.jboss.dmr.ModelNode;
Expand All @@ -58,6 +62,7 @@
import org.jboss.msc.value.InjectedValue;
import org.wildfly.extension.elytron.TrivialService.ValueSupplier;
import org.wildfly.security.authz.MappedRoleMapper;
import org.wildfly.security.authz.RegexRoleMapper;
import org.wildfly.security.authz.RoleMapper;
import org.wildfly.security.authz.Roles;

Expand All @@ -80,6 +85,19 @@ class RoleMapperDefinitions {
.setRestartAllServices()
.build();

static final SimpleAttributeDefinition REGEX = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.REGEX, ModelType.STRING, false)
.setAllowExpression(true)
.setMinSize(1)
.setRestartAllServices()
.setValidator(new RegexValidator())
.build();

static final SimpleAttributeDefinition REPLACEMENT = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.REPLACEMENT, ModelType.STRING, false)
.setAllowExpression(true)
.setMinSize(1)
.setRestartAllServices()
.build();

static final SimpleAttributeDefinition LEFT = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.LEFT, ModelType.STRING, true)
.setMinSize(1)
.setRestartAllServices()
Expand Down Expand Up @@ -173,6 +191,42 @@ protected ValueSupplier<RoleMapper> getValueSupplier(OperationContext context, M
return new RoleMapperResourceDefinition(ElytronDescriptionConstants.MAPPED_ROLE_MAPPER, add, ROLE_MAPPING_MAP, KEEP_MAPPED, KEEP_NON_MAPPED);
}

static class RegexValidator implements ParameterValidator {

@Override
public void validateParameter(String parameterName, ModelNode value) throws OperationFailedException {
if (value.isDefined()) {
String regex = value.asString();
try {
Pattern.compile(regex); // make sure the input is valid regex
} catch (PatternSyntaxException exception) {
throw ROOT_LOGGER.invalidRegex(regex);
}
}
}
}

static ResourceDefinition getRegexRoleMapperDefinition() {
AbstractAddStepHandler add = new RoleMapperAddHandler(REGEX, REPLACEMENT) {

@Override
protected ValueSupplier<RoleMapper> getValueSupplier(OperationContext context, ModelNode model) throws OperationFailedException {
final String regex = REGEX.resolveModelAttribute(context, model).asString();
final String replacement = REPLACEMENT.resolveModelAttribute(context, model).asString();

final RegexRoleMapper roleMapper = RegexRoleMapper.builder()
.setRegex(regex)
.setReplacement(replacement)
.build();

return () -> roleMapper;

}
};

return new RoleMapperResourceDefinition(ElytronDescriptionConstants.REGEX_ROLE_MAPPER, add, REGEX, REPLACEMENT);
}

static AggregateComponentDefinition<RoleMapper> getAggregateRoleMapperDefinition() {
return AGGREGATE_ROLE_MAPPER;
}
Expand Down
Expand Up @@ -575,4 +575,7 @@ public interface ElytronSubsystemMessages extends BasicLogger {
@Message(id = 1065, value = "Multiple maximum-cert-path definitions found.")
OperationFailedException multipleMaximumCertPathDefinitions();

@Message(id = 1066, value = "Value '%s' is not valid regex.")
OperationFailedException invalidRegex(String regex);

}
Expand Up @@ -665,6 +665,14 @@ elytron.mapped-role-mapper.keep-mapped=When set to 'true' the mapped roles will
elytron.mapped-role-mapper.keep-non-mapped=When set to 'true' the mapped roles will retain all roles, that have no defined mappings.
elytron.mapped-role-mapper.role-map=A string to string list map for mapping roles.

elytron.regex-role-mapper=A RoleMapper definition for a RoleMapper that performs a mapping based on regex and replaces matching roles with replacement pattern.
# Operations
elytron.regex-role-mapper.add=The add operation for the role mapper.
elytron.regex-role-mapper.remove=The remove operation for the role mapper.
# Attributes
elytron.regex-role-mapper.regex=Regex string that will be used for matching.
elytron.regex-role-mapper.replacement=Replacement pattern that will be used when replacing matching roles.

#####################
# Realm Definitions #
#####################
Expand Down
27 changes: 27 additions & 0 deletions elytron/src/main/resources/schema/wildfly-elytron_9_0.xsd
Expand Up @@ -2489,6 +2489,7 @@
<xs:element name="custom-role-mapper" type="customRoleMapperType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="logical-role-mapper" type="logicalRoleMapperType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="mapped-role-mapper" type="mappedRoleMapperType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="regex-role-mapper" type="regexRoleMapperType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="x500-subject-evidence-decoder" type="x500SubjectEvidenceDecoderType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="x509-subject-alt-name-evidence-decoder" type="x509SubjectAltNameEvidenceDecoderType" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="custom-evidence-decoder" type="customEvidenceDecoderType" minOccurs="0" maxOccurs="unbounded" />
Expand Down Expand Up @@ -3513,6 +3514,32 @@
</xs:complexContent>
</xs:complexType>

<xs:complexType name="regexRoleMapperType">
<xs:annotation>
<xs:documentation>
A RoleMapper definition that uses regex to find matching roles and then replaces these roles with replacement pattern.
</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="roleMapperType">
<xs:attribute name="regex" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
The regex used for matching.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="replacement" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
The replacement pattern.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>

<xs:complexType name="roleMapperRefType">
<xs:annotation>
<xs:documentation>
Expand Down
@@ -0,0 +1,120 @@
package org.wildfly.extension.elytron;

import org.jboss.as.controller.client.helpers.ClientConstants;
import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
import org.jboss.as.subsystem.test.KernelServices;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceName;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.wildfly.security.auth.server.SecurityDomain;
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.auth.server.ServerAuthenticationContext;
import org.wildfly.security.authz.Roles;

import java.io.IOException;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;

public class RegexRoleMapperTestCase extends AbstractSubsystemBaseTest {
private KernelServices services = null;

public RegexRoleMapperTestCase() {
super(ElytronExtension.SUBSYSTEM_NAME, new ElytronExtension());
}

@Override
protected String getSubsystemXml() throws IOException {
return readResource("role-mappers-test.xml");
}

private void init(String... domainsToActivate) throws Exception {
services = super.createKernelServicesBuilder(new TestEnvironment()).setSubsystemXmlResource("role-mappers-test.xml").build();
if (!services.isSuccessfulBoot()) {
Assert.fail(services.getBootError().toString());
}
TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "TestDomain5");
TestEnvironment.activateService(services, Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY, "TestDomain6");
}

@Test
public void testMappedRoleMapper() throws Exception {
init("TestDomain5");

ServiceName serviceName = Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY.getCapabilityServiceName("TestDomain5");
Assert.assertNotNull(services.getContainer());
Assert.assertNotNull(services.getContainer().getService(serviceName));
SecurityDomain domain = (SecurityDomain) services.getContainer().getService(serviceName).getValue();
Assert.assertNotNull(domain);

ServerAuthenticationContext context = domain.createNewAuthenticationContext();
context.setAuthenticationName("user2");
Assert.assertTrue(context.exists());
Assert.assertTrue(context.authorize());
context.succeed();
SecurityIdentity identity = context.getAuthorizedIdentity();

Roles roles = identity.getRoles();
Assert.assertTrue(roles.contains("application-user"));
Assert.assertFalse(roles.contains("123-user"));
Assert.assertFalse(roles.contains("joe"));
Assert.assertEquals("user2", identity.getPrincipal().getName());
}

@Test
public void testMappedRoleMapper2() throws Exception {
init("TestDomain6");

ServiceName serviceName = Capabilities.SECURITY_DOMAIN_RUNTIME_CAPABILITY.getCapabilityServiceName("TestDomain6");
Assert.assertNotNull(services.getContainer());
Assert.assertNotNull(services.getContainer().getService(serviceName));
SecurityDomain domain = (SecurityDomain) services.getContainer().getService(serviceName).getValue();
Assert.assertNotNull(domain);

ServerAuthenticationContext context = domain.createNewAuthenticationContext();
context.setAuthenticationName("user3");
Assert.assertTrue(context.exists());
Assert.assertTrue(context.authorize());
context.succeed();
SecurityIdentity identity = context.getAuthorizedIdentity();

Roles roles = identity.getRoles();
Assert.assertTrue(roles.contains("admin"));
Assert.assertTrue(roles.contains("user"));
Assert.assertFalse(roles.contains("joe"));
Assert.assertFalse(roles.contains("application-user"));
Assert.assertFalse(roles.contains("123-admin-123"));
Assert.assertFalse(roles.contains("aa-user-aa"));
Assert.assertEquals("user3", identity.getPrincipal().getName());
}

@Test
public void testAddRegexRoleMapperWillFailWithInvalidRegexAttribute() {
ModelNode operation = new ModelNode();
operation.get(ClientConstants.OP_ADDR).add("subsystem", "elytron").add("regex-role-mapper", "my-regex-role-mapper");
operation.get(ClientConstants.OP).set(ClientConstants.ADD);
operation.get(ElytronDescriptionConstants.REGEX).set("*-admin");
operation.get(ElytronDescriptionConstants.REPLACEMENT).set("$1");
ModelNode response = services.executeOperation(operation);
// operation will fail because regex is not valid (starts with asterisk)
if (! response.get(OUTCOME).asString().equals(FAILED)) {
Assert.fail(response.toJSONString(false));
}
}

@Before
public void init() throws Exception {
String subsystemXml;
if (JdkUtils.isIbmJdk()) {
subsystemXml = "tls-ibm.xml";
} else {
subsystemXml = JdkUtils.getJavaSpecVersion() <= 12 ? "tls-sun.xml" : "tls-oracle13plus.xml";
}
services = super.createKernelServicesBuilder(new TestEnvironment()).setSubsystemXmlResource(subsystemXml).build();
if (!services.isSuccessfulBoot()) {
Assert.fail(services.getBootError().toString());
}
}
}
Expand Up @@ -151,7 +151,9 @@ public void testRejectingTransformersEAP720() throws Exception {
.addFailedAttribute(SUBSYSTEM_ADDRESS.append(PathElement.pathElement(ElytronDescriptionConstants.TRUST_MANAGER, "TestingTrustManager")),
FailedOperationTransformationConfig.REJECTED_RESOURCE)
.addFailedAttribute(SUBSYSTEM_ADDRESS.append(PathElement.pathElement(AGGREGATE_REALM, "AggregateTwo")), REJECTED_RESOURCE)
);
.addFailedAttribute(SUBSYSTEM_ADDRESS.append(PathElement.pathElement(ElytronDescriptionConstants.REGEX_ROLE_MAPPER, "RegexRoleMapper")),
FailedOperationTransformationConfig.REJECTED_RESOURCE
));
}

@Test
Expand Down
Expand Up @@ -116,6 +116,7 @@
<evidence-decoder name="rfc822Decoder"/>
<evidence-decoder name="subjectDecoder"/>
</aggregate-evidence-decoder>
<regex-role-mapper name="RegexRoleMapper" regex=".*-user" replacement="user"/>
</mappers>
<permission-sets>
<permission-set name="my-permissions">
Expand Down
Expand Up @@ -86,6 +86,7 @@
<evidence-decoder name="subjectDecoder"/>
</aggregate-evidence-decoder>
<regex-principal-transformer name="RegexOne" pattern="a" replacement="b" />
<regex-role-mapper name="RegexRoleMapper" regex=".*-user" replacement="user"/>
</mappers>
<tls>
<key-stores>
Expand Down
@@ -1 +1,3 @@
user1=firstGroup,secondGroup
user1=firstGroup,secondGroup
user2=joe,123-user
user3=123-admin-123,joe,aa-user-aa
Expand Up @@ -191,6 +191,7 @@
<evidence-decoder name="rfc822Decoder"/>
<evidence-decoder name="subjectDecoder"/>
</aggregate-evidence-decoder>
<regex-principal-transformer name="RegexRoleMapper" pattern=".*-user" replacement="user"/>
</mappers>
<permission-sets>
<permission-set name="my-permissions">
Expand Down
Expand Up @@ -116,6 +116,7 @@
<evidence-decoder name="rfc822Decoder"/>
<evidence-decoder name="subjectDecoder"/>
</aggregate-evidence-decoder>
<regex-role-mapper name="RegexRoleMapper" regex=".*-user" replacement="user"/>
</mappers>
<permission-sets>
<permission-set name="my-permissions">
Expand Down
Expand Up @@ -16,6 +16,12 @@
<security-domain name="TestDomain4" default-realm="ClearPropertyRealm" permission-mapper="ConstantPermissionMapper">
<realm name="ClearPropertyRealm" role-mapper="KeepBothMappedRoleMapper" role-decoder="GroupsToRoles"/>
</security-domain>
<security-domain name="TestDomain5" default-realm="ClearPropertyRealm" permission-mapper="ConstantPermissionMapper">
<realm name="ClearPropertyRealm" role-mapper="MyRegexMapper" role-decoder="GroupsToRoles"/>
</security-domain>
<security-domain name="TestDomain6" default-realm="ClearPropertyRealm" permission-mapper="ConstantPermissionMapper">
<realm name="ClearPropertyRealm" role-mapper="MyRegexMapper2" role-decoder="GroupsToRoles"/>
</security-domain>
</security-domains>
<security-realms>
<properties-realm name="ClearPropertyRealm">
Expand Down Expand Up @@ -44,6 +50,8 @@
<role-mapping from="firstGroup" to="mappedGroup"/>
<role-mapping from="dontMap" to="notInThisGroup"/>
</mapped-role-mapper>
<regex-role-mapper name="MyRegexMapper" regex=".*-(user)" replacement="application-$1"/>
<regex-role-mapper name="MyRegexMapper2" regex=".*-([a-zA-Z]*)-.*" replacement="$1"/>
</mappers>
<permission-sets>
<permission-set name="login-permission">
Expand Down

0 comments on commit 7799670

Please sign in to comment.