Skip to content
This repository has been archived by the owner on Jan 5, 2021. It is now read-only.

Commit

Permalink
Resolves wildfly-security#123 Port over code to AuthenticationManager…
Browse files Browse the repository at this point in the history
… to configure a DeploymentInfo to activate Elytron based authentication.
  • Loading branch information
darranl committed Feb 28, 2018
1 parent d6321dc commit 3623d79
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,44 @@

package org.wildfly.elytron.web.undertow.server.servlet;

import static java.security.AccessController.doPrivileged;
import static org.wildfly.elytron.web.undertow.server.servlet.ElytronHttpServletExchange.APPLICATION_SCOPE_RESOLVER;
import static org.wildfly.elytron.web.undertow.server.servlet.IdentityMapping.mapIdentity;
import static org.wildfly.security.http.HttpConstants.CONFIG_CONTEXT_PATH;
import static org.wildfly.security.http.HttpConstants.CONFIG_ERROR_PAGE;
import static org.wildfly.security.http.HttpConstants.CONFIG_LOGIN_PAGE;
import static org.wildfly.security.http.HttpConstants.CONFIG_REALM;

import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.UnaryOperator;

import org.jboss.metadata.javaee.jboss.RunAsIdentityMetaData;
import org.wildfly.elytron.web.undertow.server.ElytronContextAssociationHandler;
import org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler;
import org.wildfly.elytron.web.undertow.server.ScopeSessionListener;
import org.wildfly.security.auth.server.HttpAuthenticationFactory;
import org.wildfly.security.auth.server.SecurityDomain;
import org.wildfly.security.http.HttpAuthenticationException;
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
import org.wildfly.security.http.Scope;
import org.wildfly.security.http.util.PropertiesServerMechanismFactory;
import org.wildfly.security.manager.WildFlySecurityManager;

import io.undertow.server.HttpHandler;
import io.undertow.servlet.api.AuthMethodConfig;
import io.undertow.servlet.api.AuthorizationManager;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.LoginConfig;

/**
* A utility class to take relevant Elytron component instances and apply them to an Undertow {@link DeploymentInfo} to apply
Expand All @@ -31,20 +63,147 @@
*/
public class AuthenticationManager {

private final Builder builder;

AuthenticationManager(Builder builder) {
// A builder can only be built once and further modifications are prohibited so we can cache it.
this.builder = builder;
}

/**
* Configure the {@link DeploymentInfo} so the deployment will use Elytron security based on the parameters used to
* initialise this {@link AuthenticationManaer}.
*
* @param deploymentInfo the {@link DeploymentInfo} to configure.
*/
public void configure(DeploymentInfo deploymentInfo) {
final ScopeSessionListener scopeSessionListener = ScopeSessionListener.builder()
.addScopeResolver(Scope.APPLICATION, APPLICATION_SCOPE_RESOLVER)
.build();

final SecurityDomain securityDomain = builder.httpAuthenticationFactory.getSecurityDomain();

if (WildFlySecurityManager.isChecking()) {
doPrivileged((PrivilegedAction<Void>) () -> {
securityDomain.registerWithClassLoader(deploymentInfo.getClassLoader());
return null;
});
} else {
securityDomain.registerWithClassLoader(deploymentInfo.getClassLoader());
}

deploymentInfo.addSessionListener(scopeSessionListener);

final Function<String, RunAsIdentityMetaData> runAsMapper = builder.runAsMapper;
deploymentInfo.addInnerHandlerChainWrapper(h -> finalSecurityHandlers(h, securityDomain, runAsMapper));
deploymentInfo.setInitialSecurityWrapper(h -> initialSecurityHandler(deploymentInfo, h, securityDomain, scopeSessionListener));
if (runAsMapper != null) {
deploymentInfo.addLifecycleInterceptor(new RunAsLifecycleInterceptor(runAsMapper, securityDomain));
}

if (builder.authorizationManager != null) {
deploymentInfo.setAuthorizationManager(builder.authorizationManager);
} else {
// TODO - If we can discover the SecurityDomain at runtime this could also become a singleton.
deploymentInfo.setAuthorizationManager(new ElytronAuthorizationManager(securityDomain));
}
}

private HttpHandler initialSecurityHandler(final DeploymentInfo deploymentInfo, HttpHandler toWrap, SecurityDomain securityDomain, ScopeSessionListener scopeSessionListener) {
final Collection<String> availableMechanisms = builder.httpAuthenticationFactory.getMechanismNames();
if (availableMechanisms.isEmpty()) {
throw new IllegalStateException("There are no mechanisms available from the HttpAuthenticationFactory.");
//throw ROOT_LOGGER.noMechanismsAvailable();
}

Map<String, String> tempBaseConfiguration = new HashMap<>();
tempBaseConfiguration.put(CONFIG_CONTEXT_PATH, deploymentInfo.getContextPath());

LoginConfig loginConfig = deploymentInfo.getLoginConfig();
if (loginConfig != null) {
String realm = loginConfig.getRealmName();
if (realm != null) tempBaseConfiguration.put(CONFIG_REALM, realm);
String loginPage = loginConfig.getLoginPage();
if (loginPage != null) tempBaseConfiguration.put(CONFIG_LOGIN_PAGE, loginPage);
String errorPage = loginConfig.getErrorPage();
if (errorPage != null) tempBaseConfiguration.put(CONFIG_ERROR_PAGE, errorPage);
}
final Map<String, String> baseConfiguration = Collections.unmodifiableMap(tempBaseConfiguration);

final Map<String, Map<String, String>> selectedMechanisms = new LinkedHashMap<>();
if (builder.overrideDeploymentConfig || (loginConfig == null)) {
final Map<String, String> mechanismConfiguration = baseConfiguration;
for (String n : availableMechanisms) {
selectedMechanisms.put(n, mechanismConfiguration);
}
} else {
final List<AuthMethodConfig> authMethods = loginConfig.getAuthMethods();
if (authMethods.isEmpty()) {
throw new IllegalStateException("No authentication mechanisms have been selected.");
//throw ROOT_LOGGER.noMechanismsSelected();
}
for (AuthMethodConfig c : authMethods) {
String name = c.getName();
if (availableMechanisms.contains(name) == false) {
throw new IllegalStateException(String.format("The required mechanism '%s' is not available in mechanisms %s from the HttpAuthenticationFactory.", name, availableMechanisms));
//throw ROOT_LOGGER.requiredMechanismNotAvailable(name, availableMechanisms);
}

Map<String, String> mechanismConfiguration;
Map<String, String> additionalProperties = c.getProperties();
if (additionalProperties != null) {
mechanismConfiguration = new HashMap<>(baseConfiguration);
mechanismConfiguration.putAll(additionalProperties);
mechanismConfiguration = Collections.unmodifiableMap(mechanismConfiguration);
} else {
mechanismConfiguration = baseConfiguration;
}
selectedMechanisms.put(name, mechanismConfiguration);
}
}

return ElytronContextAssociationHandler.builder()
.setNext(toWrap)
.setSecurityDomain(securityDomain)
.setMechanismSupplier(() -> getAuthenticationMechanisms(selectedMechanisms))
.setHttpExchangeSupplier(httpServerExchange -> new ElytronHttpServletExchange(httpServerExchange, scopeSessionListener))
.build();
}

private HttpHandler finalSecurityHandlers(HttpHandler toWrap, final SecurityDomain securityDomain, final Function<String, RunAsIdentityMetaData> runAsMapper) {
return runAsMapper != null ? new ElytronRunAsHandler(toWrap, (s, e) -> mapIdentity(s, securityDomain, e, runAsMapper)) : new ElytronRunAsHandler(toWrap);
}

private List<HttpServerAuthenticationMechanism> getAuthenticationMechanisms(Map<String, Map<String, String>> selectedMechanisms) {
List<HttpServerAuthenticationMechanism> mechanisms = new ArrayList<>(selectedMechanisms.size());
UnaryOperator<HttpServerAuthenticationMechanismFactory> singleSignOnTransformer = builder.httpAuthenticationFactoryTransformer;
for (Entry<String, Map<String, String>> entry : selectedMechanisms.entrySet()) {
try {
UnaryOperator<HttpServerAuthenticationMechanismFactory> factoryTransformation = f -> {
HttpServerAuthenticationMechanismFactory factory = new PropertiesServerMechanismFactory(f, entry.getValue());
return (singleSignOnTransformer != null) ? singleSignOnTransformer.apply(factory) : factory;
};
HttpServerAuthenticationMechanism mechanism = builder.httpAuthenticationFactory.createMechanism(entry.getKey(), factoryTransformation);
if (mechanism != null) mechanisms.add(mechanism);
} catch (HttpAuthenticationException e) {
throw new IllegalStateException(e);
}
}

return mechanisms;
}

public static Builder builder() {
return new Builder();
}

static class Builder {
public static class Builder {

private HttpAuthenticationFactory httpAuthenticationFactory;
private boolean enableJacc;
private boolean overrideDeploymentConfig;
private AuthorizationManager authorizationManager;
private UnaryOperator<HttpServerAuthenticationMechanismFactory> httpAuthenticationFactoryTransformer;
private Function<String, RunAsIdentityMetaData> runAsMapper;

private boolean built = false;

Expand All @@ -62,14 +221,14 @@ public Builder setHttpAuthenticationFactory(final HttpAuthenticationFactory http
}

/**
* Sets if JACC should be enabled for the deployment.
* Set an {@link AuthorizationManager} for the deployment, if none is provided the default Elytron AuthorizationManager will be used instead.
*
* @param enableJacc should jacc be enabled for this deployment.
* @param authorizationManagerSupplier an {@link AuthorizationManager} for the deployment.
* @return this {@link Builder}
*/
public Builder setEnableJacc(final boolean enableJacc) {
public Builder setAuthorizationManager(final AuthorizationManager authorizationManager) {
assertNotBuilt();
this.enableJacc = enableJacc;
this.authorizationManager = authorizationManager;

return this;
}
Expand Down Expand Up @@ -101,6 +260,24 @@ public Builder setHttpAuthenticationFactoryTransformer (final UnaryOperator<Http
return this;
}

/**
* Set the run as mapper for identfying run {@link RunAsIdentityMetaData}.
*
* @param runAsMapper the run as mapper for identfying run {@link RunAsIdentityMetaData}.
* @return this {@link Builder}
*/
public Builder setRunAsMapper(final Function<String, RunAsIdentityMetaData> runAsMapper) {
assertNotBuilt();
this.runAsMapper = runAsMapper;

return this;
}

/**
* Assemble the supplied configuration into a complete {@link AuthenticationManager}.
*
* @return a configured {@link AuthenticationManager}.
*/
public AuthenticationManager build() {
assertNotBuilt();
built = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.function.Consumer;
import java.util.function.Function;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
Expand Down Expand Up @@ -57,6 +58,8 @@ class ElytronHttpServletExchange extends ElytronHttpExchange {
private final HttpServerExchange httpServerExchange;
private final ScopeSessionListener scopeSessionListener;

static Function<HttpServerExchange, HttpScope> APPLICATION_SCOPE_RESOLVER = ElytronHttpServletExchange::applicationScope;

protected ElytronHttpServletExchange(final HttpServerExchange httpServerExchange, final ScopeSessionListener scopeSessionListener) {
super(httpServerExchange);
this.httpServerExchange = httpServerExchange;
Expand Down Expand Up @@ -129,7 +132,7 @@ public HttpScope getScope(Scope scope) {
}
}

static HttpScope applicationScope(HttpServerExchange exchange) {
private static HttpScope applicationScope(HttpServerExchange exchange) {
ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);

if (servletRequestContext != null) {
Expand Down

0 comments on commit 3623d79

Please sign in to comment.