Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright The WildFly Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

import java.io.IOException;
import java.security.Principal;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.wildfly.security.evidence.PasswordGuessEvidence;

/**
* @author <a href="mailto:pesilva@redhat.com">Pedro Hos</a>
*
*/
public class CustomCallbackHandler implements CallbackHandler {

private Principal principal;
private char[] evidence;

public CustomCallbackHandler() {}

public void setSecurityInfo(final Principal principal, final Object evidence) {
this.principal = principal;
if(evidence instanceof PasswordGuessEvidence) {
this.evidence = ((PasswordGuessEvidence) evidence).getGuess();
}
}

@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

if (callbacks == null) {
throw new IllegalArgumentException("The callbacks argument cannot be null");
}

for (Callback callback : callbacks) {
if (callback instanceof NameCallback && principal != null) {
NameCallback nameCallback = (NameCallback) callback;
nameCallback.setName(this.principal.getName());
} else if (callback instanceof PasswordCallback) {
PasswordCallback passwordCallback = (PasswordCallback) callback;
passwordCallback.setPassword((this.evidence));
} else {
throw new UnsupportedCallbackException(callback, "Unsupported callback");
}
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright The WildFly Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;

import org.infinispan.Cache;
import org.infinispan.commons.configuration.ClassAllowList;
import org.infinispan.commons.marshall.JavaSerializationMarshaller;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
import org.jgroups.util.UUID;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.wildfly.elytron.web.undertow.common.UndertowServer;
import org.wildfly.security.auth.permission.LoginPermission;
import org.wildfly.security.auth.realm.JaasSecurityRealm;
import org.wildfly.security.auth.server.SecurityDomain;
import org.wildfly.security.auth.server.SecurityRealm;
import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
import org.wildfly.security.http.util.sso.DefaultSingleSignOnManager;
import org.wildfly.security.http.util.sso.DefaultSingleSignOnSessionFactory;
import org.wildfly.security.http.util.sso.DefaultSingleSignOnSessionIdentifierFactory;
import org.wildfly.security.http.util.sso.SingleSignOnEntry;
import org.wildfly.security.http.util.sso.SingleSignOnManager;
import org.wildfly.security.http.util.sso.SingleSignOnServerMechanismFactory;
import org.wildfly.security.http.util.sso.SingleSignOnSessionFactory;

/**
* @author <a href="mailto:pesilva@redhat.com">Pedro Hos</a>
*
*/
public class JAASFormServletAuthenticationWithClusterTestTest extends FormAuthenticationSSOBase {

@BeforeClass
public static void beforeClass() {
System.setProperty("java.security.auth.login.config", JAASFormServletAuthenticationWithClusterTestTest.class.getResource("jaas-login.config").toString());
}

@AfterClass
public static void afterClass() {
System.clearProperty("java.security.auth.login.config");
}

@Rule
public final UndertowServer serverA = createUndertowServer(7776);

@Rule
public final UndertowServer serverB = createUndertowServer(7777);

public JAASFormServletAuthenticationWithClusterTestTest() throws Exception {
}

@Override
protected URI createUriAppA(String alternativePath) throws URISyntaxException {
return serverA.createUri(alternativePath);
}

@Override
protected URI createUriAppB(String alternativePath) throws URISyntaxException {
return serverB.createUri(alternativePath);
}

@Override
protected String getContextRootAppA() {
return serverA.getContextRoot();
}

@Override
protected String getContextRootAppB() {
return serverB.getContextRoot();
}

@Override
protected HttpServerAuthenticationMechanismFactory getHttpServerAuthenticationMechanismFactory(Map<String, ?> properties) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@darranl The test isn't work properly yet when running under cluster mode. I suspect that I need to change something here, on cache configuration. The fix works on https://issues.redhat.com/browse/WFLY-18650 reproducer, but under the undertow test here, the second node can't see the roles, and can't login properly. Do you have any light on how to configure the HttpServerAuthenticationMechanismFactory to make it work? Thank you

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pedro-hos Please try to run your test on top of this PR wildfly-security/wildfly-elytron#2294 , thank you!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Skyllarr great, all tests are green w/ your change. Thank You

HttpServerAuthenticationMechanismFactory delegate = super.getHttpServerAuthenticationMechanismFactory(properties);

String cacheManagerName = UUID.randomUUID().toString();
ClassAllowList allowList = new ClassAllowList();
allowList.addRegexps(".*");

EmbeddedCacheManager cacheManager = new DefaultCacheManager(
GlobalConfigurationBuilder.defaultClusteredBuilder()
.globalJmxStatistics().cacheManagerName(cacheManagerName).defaultCacheName("Default")
.transport().nodeName(cacheManagerName).addProperty(JGroupsTransport.CONFIGURATION_FILE, "fast.xml")
.serialization().marshaller(new JavaSerializationMarshaller(allowList))
.build(),
new ConfigurationBuilder()
.clustering()
.cacheMode(CacheMode.REPL_SYNC)
.build()
);

Cache<String, SingleSignOnEntry> cache = cacheManager.getCache();
SingleSignOnManager manager = new DefaultSingleSignOnManager(cache, new DefaultSingleSignOnSessionIdentifierFactory(), (id, entry) -> cache.put(id, entry));
SingleSignOnServerMechanismFactory.SingleSignOnConfiguration signOnConfiguration =
new SingleSignOnServerMechanismFactory.SingleSignOnConfiguration("JSESSIONSSOID", null,
"/", false, false);

if (keyPairSupplier == null) {
keyPairSupplier = new KeyPairSupplier();
}
SingleSignOnSessionFactory singleSignOnSessionFactory = new DefaultSingleSignOnSessionFactory(manager, keyPairSupplier.get());

return new SingleSignOnServerMechanismFactory(delegate, singleSignOnSessionFactory, signOnConfiguration);
}

@Override
protected UndertowServer createUndertowServer(int port) throws Exception {
return createUndertowServerBuilder(port).setSecurityRoles("Admin")
.setRoleAllowed("Admin")
.build();
}

@Override
protected SecurityDomain doCreateSecurityDomain() throws Exception {
SecurityRealm realm = new JaasSecurityRealm("Entry1", null, null, new CustomCallbackHandler());
//SecurityRealm realm = new JaasSecurityRealm("Entry1");
SecurityDomain securityDomain = SecurityDomain.builder().setDefaultRealmName("default")
.addRealm("default", realm).build()
.setPermissionMapper(((permissionMappable, roles) -> LoginPermission.getInstance()))
.build();
return securityDomain;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright The WildFly Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

import java.io.IOException;
import java.security.Principal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import org.wildfly.security.auth.principal.NamePrincipal;

/**
* @author <a href="mailto:pesilva@redhat.com">Pedro Hos</a>
*
*/
public class TestLoginModule implements LoginModule {

private final Map<String, char[]> usersMap = new HashMap<String, char[]>();
private Principal principal;
private Subject subject;
private CallbackHandler handler;

@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
this.subject = subject;
this.handler = callbackHandler;
this.usersMap.put("ladybird", "Coleoptera".toCharArray());
this.usersMap.put("dung", "Coleopterida".toCharArray());
}

@Override
public boolean login() throws LoginException {

// obtain the incoming username and password from the callback handler
NameCallback nameCallback = new NameCallback("Username");
PasswordCallback passwordCallback = new PasswordCallback("Password", false);
Callback[] callbacks = new Callback[] { nameCallback, passwordCallback };

try {
this.handler.handle(callbacks);
} catch (UnsupportedCallbackException | IOException e) {
throw new LoginException("Error handling callback: " + e.getMessage());
}

final String username = nameCallback.getName();
this.setPrincipal(new NamePrincipal(username));

final char[] password = passwordCallback.getPassword();

char[] storedPassword = this.usersMap.get(username);
if (!Arrays.equals(storedPassword, password)) {
throw new LoginException("Invalid password");
} else {
return true;
}
}

@Override
public boolean commit() throws LoginException {
if (this.principal.getName().equals("ladybird") || this.principal.getName().equals("dung")) {
this.subject.getPrincipals().add(new Roles("Admin"));
this.subject.getPrincipals().add(new Roles("User"));
this.subject.getPrincipals().add(new Roles("Guest"));
}
return true;
}

@Override
public boolean abort() throws LoginException {
return true;
}

@Override
public boolean logout() throws LoginException {
this.subject.getPrincipals().clear();
return true;
}

public Principal getPrincipal() {
return principal;
}

public void setPrincipal(Principal principal) {
this.principal = principal;
}

public static class Roles implements Principal {

private String role;

Roles(String role) {
this.role = role;
}

@Override
public String getName() {
return this.role;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,34 @@ public class UndertowServletServer extends UndertowServer {
private final String authenticationMechanism;
private final String deploymentName;
private final Map<String, String> additionalDeployments;
private final String securityRoles;
private final String roleAllowed;

private Undertow undertowServer;

protected UndertowServletServer(Supplier<SSLContext> serverSslContext, int port, String contextRoot, final String authenticationMechanism,
final SecurityDomain securityDomain, final HttpServerAuthenticationMechanismFactory httpServerAuthenticationMechanismFactory, final String deploymentName,
Map<String, String> additionalDeployments) {
Map<String, String> additionalDeployments, String securityRoles, String roleAllowed) {
super(serverSslContext, port, contextRoot, SERVLET);
this.authenticationMechanism = authenticationMechanism;
this.securityDomain = securityDomain;
this.httpServerAuthenticationMechanismFactory = httpServerAuthenticationMechanismFactory;
this.deploymentName = deploymentName;
this.additionalDeployments = additionalDeployments;
this.securityRoles = securityRoles;
this.roleAllowed = roleAllowed;
}

private DeploymentInfo createDeployment(final String deploymentName, final String contextRoot) {
return Servlets.deployment()
DeploymentInfo deployment = Servlets.deployment()
.setClassLoader(TestServlet.class.getClassLoader())
.setContextPath(contextRoot)
.setDeploymentName(deploymentName)
.setLoginConfig(new LoginConfig(authenticationMechanism, "Elytron Realm", "/login", "/error"))
.addSecurityConstraint(new SecurityConstraint()
.addWebResourceCollection(new WebResourceCollection()
.addUrlPattern(SERVLET + "/*"))
.addRoleAllowed("**")
.addRoleAllowed(roleAllowed.isEmpty() ? "**" : roleAllowed)
.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.DENY))
.addServlets(Servlets.servlet(TestServlet.class)
.addMapping("/")
Expand All @@ -93,6 +97,12 @@ private DeploymentInfo createDeployment(final String deploymentName, final Strin
.addMapping("/login"),
Servlets.servlet(LogoutServlet.class)
.addMapping("/logout"));

if(!securityRoles.isEmpty()) {
deployment.addSecurityRoles(securityRoles);
}

return deployment;
}

@Override
Expand Down Expand Up @@ -162,6 +172,8 @@ public static class Builder {
private HttpServerAuthenticationMechanismFactory httpServerAuthenticationMechanismFactory;
String deploymentName = "helloworld.war";
private Map<String, String> additionalDeployments = new HashMap<>();
private String securityRoles = "";
private String roleAllowed = "";

public Builder setAuthenticationMechanism(final String authenticationMechanism) {
this.authenticationMechanism = authenticationMechanism;
Expand Down Expand Up @@ -215,9 +227,19 @@ public Builder addAdditionalDeployment(final String deploymentName, final String
return this;
}

public Builder setSecurityRoles(final String securityRoles) {
this.securityRoles = securityRoles;
return this;
}

public Builder setRoleAllowed(final String roleAllowed) {
this.roleAllowed = roleAllowed;
return this;
}

public UndertowServer build() throws Exception {
return new UndertowServletServer(serverSslContext, port, contextRoot, authenticationMechanism,
securityDomain, httpServerAuthenticationMechanismFactory, deploymentName, additionalDeployments);
securityDomain, httpServerAuthenticationMechanismFactory, deploymentName, additionalDeployments, securityRoles, roleAllowed);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Entry1 {
org.wildfly.elytron.web.undertow.server.servlet.TestLoginModule required;
};
Loading