Skip to content

Commit

Permalink
[WFLY-5774] Add security domain to Elytron mappings to the Undertow s…
Browse files Browse the repository at this point in the history
…ubsystem and use these for authentication if available.

If no mapping exists we continue to use the existing PicketBox security domains.
  • Loading branch information
darranl committed Dec 9, 2015
1 parent e1f935a commit 2d5b0da
Show file tree
Hide file tree
Showing 13 changed files with 433 additions and 25 deletions.
Expand Up @@ -58,6 +58,7 @@
<module name="org.jboss.as.network"/>
<module name="org.jboss.as.security"/>
<module name="org.wildfly.security.elytron"/>
<module name="org.wildfly.security.elytron-web.undertow-server"/>
<module name="org.jboss.as.server"/>
<module name="org.jboss.as.threads"/>
<module name="org.jboss.common-beans" services="import"/>
Expand Down
24 changes: 24 additions & 0 deletions undertow/pom.xml
Expand Up @@ -37,6 +37,30 @@

<packaging>jar</packaging>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
<enableAssertions>true</enableAssertions>
<argLine>-Xmx512m</argLine>
<systemProperties>
<property>
<name>jboss.home</name>
<value>${jboss.home}</value>
</property>
</systemProperties>
<includes>
<include>**/*TestCase.java</include>
</includes>
<forkMode>once</forkMode>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jboss.common</groupId>
Expand Down
@@ -0,0 +1,278 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2015, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.wildfly.extension.undertow;

import static org.jboss.as.controller.capability.RuntimeCapability.buildDynamicCapabilityName;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.PersistentResourceDefinition;
import org.jboss.as.controller.ResourceDefinition;
import org.jboss.as.controller.ServiceRemoveStepHandler;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.capability.RuntimeCapability;
import org.jboss.as.controller.registry.AttributeAccess;
import org.jboss.as.controller.registry.Resource;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.msc.inject.Injector;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController.Mode;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.jboss.msc.value.InjectedValue;
import org.wildfly.elytron.web.undertow.server.ElytronContextAssociationHandler;
import org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler;
import org.wildfly.security.auth.server.HttpAuthenticationFactory;
import org.wildfly.security.http.HttpAuthenticationException;
import org.wildfly.security.http.HttpServerAuthenticationMechanism;

import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.BlockingHandler;
import io.undertow.servlet.api.DeploymentInfo;

/**
* A {@link ResourceDefinition} to define the mapping from a security domain as specified in a web application
* to an {@link HttpAuthenticationFactory} plus additional policy information.
*
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
*/
public class ApplicationSecurityDomainDefinition extends PersistentResourceDefinition {

public static final String APPLICATION_SECURITY_DOMAIN_CAPABILITY = "org.wildfly.extension.undertow.application-security-domain";

static final RuntimeCapability<Void> APPLICATION_SECURITY_DOMAIN_RUNTIME_CAPABILITY = RuntimeCapability
.Builder.of(APPLICATION_SECURITY_DOMAIN_CAPABILITY, true, Function.class)
.build();

private static final String HTTP_AUTHENITCATION_FACTORY_CAPABILITY = "org.wildfly.security.http-server-authentication";

static SimpleAttributeDefinition HTTP_SERVER_MECHANISM_FACTORY = new SimpleAttributeDefinitionBuilder(Constants.HTTP_AUTHENITCATION_FACTORY, ModelType.STRING, false)
.setMinSize(1)
.setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES)
.setCapabilityReference(HTTP_AUTHENITCATION_FACTORY_CAPABILITY, APPLICATION_SECURITY_DOMAIN_CAPABILITY, true)
.build();

static SimpleAttributeDefinition OVERRIDE_DEPLOYMENT_CONFIG = new SimpleAttributeDefinitionBuilder(Constants.OVERRIDE_DEPLOYMENT_CONFIG, ModelType.BOOLEAN, true)
.setDefaultValue(new ModelNode(false))
.setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES)
.build();

private static final AttributeDefinition[] ATTRIBUTES = new AttributeDefinition[] { HTTP_SERVER_MECHANISM_FACTORY, OVERRIDE_DEPLOYMENT_CONFIG };

static final ApplicationSecurityDomainDefinition INSTANCE = new ApplicationSecurityDomainDefinition();

private static final Set<String> knownApplicationSecurityDomains = Collections.synchronizedSet(new HashSet<>());

private ApplicationSecurityDomainDefinition() {
this(new Parameters(PathElement.pathElement(Constants.APPLICATION_SECURITY_DOMAIN), UndertowExtension.getResolver(Constants.APPLICATION_SECURITY_DOMAIN))
.setCapabilities(APPLICATION_SECURITY_DOMAIN_RUNTIME_CAPABILITY), new AddHandler());
}

private ApplicationSecurityDomainDefinition(Parameters parameters, AbstractAddStepHandler add) {
super(parameters.setAddHandler(add).setRemoveHandler(new RemoveHandler(add)));
}

private static class AddHandler extends AbstractAddStepHandler {

private AddHandler() {
super(ATTRIBUTES);
}

/* (non-Javadoc)
* @see org.jboss.as.controller.AbstractAddStepHandler#populateModel(org.jboss.as.controller.OperationContext, org.jboss.dmr.ModelNode, org.jboss.as.controller.registry.Resource)
*/
@Override
protected void populateModel(OperationContext context, ModelNode operation, Resource resource) throws OperationFailedException {
super.populateModel(context, operation, resource);
knownApplicationSecurityDomains.add(context.getCurrentAddressValue());
}

@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {

String httpServerMechanismFactory = HTTP_SERVER_MECHANISM_FACTORY.resolveModelAttribute(context, model).asString();
boolean overrideDeploymentConfig = OVERRIDE_DEPLOYMENT_CONFIG.resolveModelAttribute(context, model).asBoolean();

RuntimeCapability<?> runtimeCapability = APPLICATION_SECURITY_DOMAIN_RUNTIME_CAPABILITY.fromBaseCapability(context.getCurrentAddressValue());
ServiceName serviceName = runtimeCapability.getCapabilityServiceName(Function.class);

ApplicationSecurityDomainService applicationSecurityDomainService = new ApplicationSecurityDomainService(overrideDeploymentConfig);

ServiceBuilder<Function<DeploymentInfo, Registration>> serviceBuilder = context.getServiceTarget().addService(serviceName, applicationSecurityDomainService)
.setInitialMode(Mode.LAZY);

serviceBuilder.addDependency(context.getCapabilityServiceName(
buildDynamicCapabilityName(HTTP_AUTHENITCATION_FACTORY_CAPABILITY, httpServerMechanismFactory), HttpAuthenticationFactory.class),
HttpAuthenticationFactory.class, applicationSecurityDomainService.getHttpAuthenticationFactoryInjector());

serviceBuilder.install();
}

}

private static class RemoveHandler extends ServiceRemoveStepHandler {

/**
* @param addOperation
*/
protected RemoveHandler(AbstractAddStepHandler addOperation) {
super(addOperation);
}

@Override
protected void performRemove(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
super.performRemove(context, operation, model);
knownApplicationSecurityDomains.remove(context.getCurrentAddressValue());
}

@Override
protected ServiceName serviceName(String name) {
RuntimeCapability<?> dynamicCapability = APPLICATION_SECURITY_DOMAIN_RUNTIME_CAPABILITY.fromBaseCapability(name);
return dynamicCapability.getCapabilityServiceName(HttpAuthenticationFactory.class);
}

}

@Override
public Collection<AttributeDefinition> getAttributes() {
return Arrays.asList(ATTRIBUTES);
}

Predicate<String> getKnownSecurityDomainPredicate() {
return knownApplicationSecurityDomains::contains;
}

private static class ApplicationSecurityDomainService implements Service<Function<DeploymentInfo, Registration>> {

private final boolean overrideDeploymentConfig;
private final InjectedValue<HttpAuthenticationFactory> httpAuthenticationFactoryInjector = new InjectedValue<>();
private final Set<Registration> registrations = new HashSet<>();

private HttpAuthenticationFactory httpAuthenticationFactory;

private ApplicationSecurityDomainService(final boolean overrideDeploymentConfig) {
this.overrideDeploymentConfig = overrideDeploymentConfig;
}

@Override
public void start(StartContext context) throws StartException {
httpAuthenticationFactory = httpAuthenticationFactoryInjector.getValue();
}

@Override
public void stop(StopContext context) {
httpAuthenticationFactory = null;
}

@Override
public Function<DeploymentInfo, Registration> getValue() throws IllegalStateException, IllegalArgumentException {
return this::applyElytronSecurity;
}

private Injector<HttpAuthenticationFactory> getHttpAuthenticationFactoryInjector() {
return httpAuthenticationFactoryInjector;
}

private Registration applyElytronSecurity(final DeploymentInfo deploymentInfo) {
deploymentInfo.addInnerHandlerChainWrapper(this::finalSecurityHandlers);
deploymentInfo.setInitialSecurityWrapper(h -> initialSecurityHandler(deploymentInfo, h));

Registration registration = new RegistrationImpl(deploymentInfo);
registrations.add(registration);
return registration;
}

private List<String> desiredMechanisms(DeploymentInfo deploymentInfo) {
if (overrideDeploymentConfig) {
return new ArrayList<>(httpAuthenticationFactory.getMechanismNames());
} else {
return deploymentInfo.getLoginConfig().getAuthMethods().stream().map(c -> c.getName())
.collect(Collectors.toList());
}
}

private HttpServerAuthenticationMechanism createMechanism(final String name) {
try {
return httpAuthenticationFactory.createMechanism(name);
} catch (HttpAuthenticationException e) {
throw new IllegalStateException(e);
}
}
private List<HttpServerAuthenticationMechanism> getAuthenticationMechanisms(Supplier<List<String>> mechanismNames) {
return mechanismNames.get().stream().map(this::createMechanism).collect(Collectors.toList());
}

private HttpHandler initialSecurityHandler(final DeploymentInfo deploymentInfo, HttpHandler toWrap) {
return new ElytronContextAssociationHandler(toWrap, () -> getAuthenticationMechanisms(() -> desiredMechanisms(deploymentInfo)));
}

private HttpHandler finalSecurityHandlers(HttpHandler toWrap) {
return new BlockingHandler(new ElytronRunAsHandler(toWrap));
}

private class RegistrationImpl implements Registration {

private final DeploymentInfo deploymentInfo;

private RegistrationImpl(DeploymentInfo deploymentInfo) {
this.deploymentInfo = deploymentInfo;
}

@Override
public void cancel() {
registrations.remove(this);
}

}

}

public interface Registration {

/**
* Cancel the registration.
*/
void cancel();

}
}
Expand Up @@ -216,4 +216,13 @@ public interface Constants {
String EXTENDED = "extended";
String MAX_BUFFERED_REQUEST_SIZE = "max-buffered-request-size";
String MAX_SESSIONS = "max-sessions";

// Elytron Integration
String APPLICATION_SECURITY_DOMAIN = "application-security-domain";
String APPLICATION_SECURITY_DOMAINS = "application-security-domains";
String HTTP_AUTHENITCATION_FACTORY = "http-authentication-factory";
String OVERRIDE_DEPLOYMENT_CONFIG = "override-deployment-config";
String REFERENCING_DEPLOYMENTS = "referencing-deployments";
String SECURITY_DOMAIN = "security-domain";

}
Expand Up @@ -76,21 +76,23 @@ class UndertowRootDefinition extends PersistentResourceDefinition {
.build();


static final AttributeDefinition[] ATTRIBUTES = {DEFAULT_VIRTUAL_HOST, DEFAULT_SERVLET_CONTAINER, DEFAULT_SERVER, INSTANCE_ID, STATISTICS_ENABLED, DEFAULT_SECURITY_DOMAIN};
static final ApplicationSecurityDomainDefinition APPLICATION_SECURITY_DOMAIN = ApplicationSecurityDomainDefinition.INSTANCE;
static final AttributeDefinition[] ATTRIBUTES = { DEFAULT_VIRTUAL_HOST, DEFAULT_SERVLET_CONTAINER, DEFAULT_SERVER, INSTANCE_ID, STATISTICS_ENABLED, DEFAULT_SECURITY_DOMAIN };
static final PersistentResourceDefinition[] CHILDREN = {
BufferCacheDefinition.INSTANCE,
ServerDefinition.INSTANCE,
ServletContainerDefinition.INSTANCE,
HandlerDefinitions.INSTANCE,
FilterDefinitions.INSTANCE
FilterDefinitions.INSTANCE,
APPLICATION_SECURITY_DOMAIN
};

public static final UndertowRootDefinition INSTANCE = new UndertowRootDefinition();

private UndertowRootDefinition() {
super(UndertowExtension.SUBSYSTEM_PATH,
UndertowExtension.getResolver(),
UndertowSubsystemAdd.INSTANCE,
new UndertowSubsystemAdd(APPLICATION_SECURITY_DOMAIN.getKnownSecurityDomainPredicate()),
ReloadRequiredRemoveStepHandler.INSTANCE);
}

Expand Down
Expand Up @@ -24,6 +24,8 @@

import io.undertow.Version;

import java.util.function.Predicate;

import org.jboss.as.controller.AbstractBoottimeAddStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
Expand Down Expand Up @@ -70,10 +72,12 @@
*/
class UndertowSubsystemAdd extends AbstractBoottimeAddStepHandler {

static final UndertowSubsystemAdd INSTANCE = new UndertowSubsystemAdd();

private UndertowSubsystemAdd() {
private final Predicate<String> knownSecurityDomain;

UndertowSubsystemAdd(Predicate<String> knownSecurityDomain) {
super(UndertowRootDefinition.ATTRIBUTES);
this.knownSecurityDomain = knownSecurityDomain;
}

/**
Expand Down Expand Up @@ -134,7 +138,7 @@ protected void execute(DeploymentProcessorTarget processorTarget) {

processorTarget.addDeploymentProcessor(UndertowExtension.SUBSYSTEM_NAME, Phase.INSTALL, Phase.INSTALL_SERVLET_INIT_DEPLOYMENT, new ServletContainerInitializerDeploymentProcessor());

processorTarget.addDeploymentProcessor(UndertowExtension.SUBSYSTEM_NAME, Phase.INSTALL, Phase.INSTALL_WAR_DEPLOYMENT, new UndertowDeploymentProcessor(defaultVirtualHost, defaultContainer, defaultServer, defaultSecurityDomain));
processorTarget.addDeploymentProcessor(UndertowExtension.SUBSYSTEM_NAME, Phase.INSTALL, Phase.INSTALL_WAR_DEPLOYMENT, new UndertowDeploymentProcessor(defaultVirtualHost, defaultContainer, defaultServer, defaultSecurityDomain, knownSecurityDomain));

}
}, OperationContext.Stage.RUNTIME);
Expand Down

0 comments on commit 2d5b0da

Please sign in to comment.