Skip to content

Commit

Permalink
[ELY-2254] Provide a LoginModule compatible security realm.
Browse files Browse the repository at this point in the history
  • Loading branch information
Skyllarr committed Nov 17, 2021
1 parent 0881f79 commit eebf4fd
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 172 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@
import org.jboss.logging.annotations.LogMessage;
import org.jboss.logging.annotations.Message;
import org.jboss.logging.annotations.MessageLogger;
import org.jboss.logging.annotations.Param;
import org.jboss.logging.annotations.ValidIdRange;
import org.jboss.logging.annotations.ValidIdRanges;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.auth.server.SecurityRealm;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;

/**
* Log messages and exceptions for Elytron.
*
Expand All @@ -55,7 +59,7 @@ interface ElytronMessages extends BasicLogger {

@LogMessage(level = Logger.Level.DEBUG)
@Message(id = 1007, value = "JAAS authentication failed for principal %s")
void debugJAASAuthenticationFailure(Principal principal, @Cause Throwable cause);
void debugInfoJaasAuthenticationFailure(Principal principal, @Cause Throwable cause);

@Message(id = 1008, value = "Failed to create login context")
RealmUnavailableException failedToCreateLoginContext(@Cause Throwable cause);
Expand Down Expand Up @@ -133,4 +137,13 @@ interface ElytronMessages extends BasicLogger {
@Message(id = 13001, value = "Realm is failing over.")
void realmFailover(@Cause RealmUnavailableException rue);

@Message(id = 13002, value = "%s does not handle a callback of type %s")
UnsupportedCallbackException unableToHandleCallback(@Param Callback callback, String callbackHandler, String callbackType);

@Message(id = 13003, value = "Failed to load JAAS configuration file.")
RealmUnavailableException failedToLoadJaasConfigFile();

@LogMessage(level = Logger.Level.DEBUG)
@Message(id = 13004, value = "JAAS logout failed for principal %s")
void debugInfoJaasLogoutFailure(Principal principal, @Cause Throwable cause);
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,24 @@ default Attributes getAttributes() throws RealmUnavailableException {
return null;
}

/**
* Get the public credentials of the realm identity.
*
* @return public credentials of the realm identity or IdentityCredentials.NONE if none exists.
*/
default IdentityCredentials getPublicCredentials() {
return IdentityCredentials.NONE;
}

/**
* Get the private credentials of the realm identity.
*
* @return private credentials of the realm identity or IdentityCredentials.NONE if none exists.
*/
default IdentityCredentials getPrivateCredentials() {
return IdentityCredentials.NONE;
}

/**
* The anonymous realm identity.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2023,6 +2023,8 @@ AuthorizedAuthenticationState doAuthorization(final boolean requireLoginPermissi

SecurityIdentity authorizedIdentity = Assert.assertNotNull(domain.transform(new SecurityIdentity(domain, authenticationPrincipal, realmInfo, authorizationIdentity, domain.getCategoryRoleMappers(), IdentityCredentials.NONE, IdentityCredentials.NONE)));
authorizedIdentity = authorizedIdentity.withPublicCredentials(publicCredentials).withPrivateCredentials(privateCredentials);
authorizedIdentity = authorizedIdentity.withPrivateCredentials(realmIdentity.getPrivateCredentials()).withPublicCredentials(realmIdentity.getPublicCredentials());

if (log.isTraceEnabled()) {
log.tracef("Authorizing principal %s.", authenticationPrincipal.getName());
if (authorizationIdentity != null) {
Expand All @@ -2046,8 +2048,8 @@ AuthorizedAuthenticationState doAuthorization(final boolean requireLoginPermissi
ElytronMessages.log.trace("Authorization succeed");
return new AuthorizedAuthenticationState(authorizedIdentity, authenticationPrincipal, realmInfo, realmIdentity, mechanismRealmConfiguration, mechanismConfiguration);
}
@Override

@Override
boolean authorize(final Principal authorizationId, final boolean authorizeRunAs) throws RealmUnavailableException {
final AuthorizedAuthenticationState authzState = doAuthorization(true);
if (authzState == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,26 @@

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.wildfly.security.auth.permission.LoginPermission;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.auth.server.SecurityDomain;
import org.wildfly.security.auth.server.ServerAuthenticationContext;
import org.wildfly.security.authz.AuthorizationIdentity;
import org.wildfly.security.auth.realm.JaasSecurityRealm;
import org.wildfly.security.auth.server.RealmIdentity;
import org.wildfly.security.auth.server.SecurityRealm;
import org.wildfly.security.credential.BearerTokenCredential;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.credential.PublicKeyCredential;
import org.wildfly.security.credential.X509CertificateChainCredential;
import org.wildfly.security.evidence.PasswordGuessEvidence;
import org.wildfly.security.evidence.X509PeerCertificateChainEvidence;

/**
* Testsuite for the {@link org.wildfly.security.auth.realm.JaasSecurityRealm}.
Expand All @@ -53,7 +58,7 @@ public static void init() {
public void testJaasSecurityRealm() throws Exception {

// create a JAAS security realm with the default callback handler.
SecurityRealm realm = new JaasSecurityRealm("test");
SecurityRealm realm = new JaasSecurityRealm( "Entry1", null, null);

// test the creation of a realm identity.
RealmIdentity realmIdentity = realm.getRealmIdentity(new NamePrincipal("elytron"));
Expand All @@ -63,6 +68,7 @@ public void testJaasSecurityRealm() throws Exception {
assertEquals("Invalid credential support", SupportLevel.UNSUPPORTED, realmIdentity.getCredentialAcquireSupport(PasswordCredential.class, "blah", null));
assertEquals("Invalid credential support", SupportLevel.UNSUPPORTED,
realmIdentity.getCredentialAcquireSupport(PublicKeyCredential.class, null, null));
assertEquals("Invalid credential support", SupportLevel.POSSIBLY_SUPPORTED, realmIdentity.getEvidenceVerifySupport(PasswordGuessEvidence.class, "blah"));

// the JAAS realm identity cannot be used to obtain credentials, so getCredential should always return null.
assertNull("Invalid non null credential", realmIdentity.getCredential(PasswordCredential.class, null));
Expand All @@ -75,35 +81,77 @@ public void testJaasSecurityRealm() throws Exception {
assertTrue(realmIdentity.verifyEvidence(new PasswordGuessEvidence("passwd12#$".toCharArray())));
AuthorizationIdentity authRealmIdentity = realmIdentity.getAuthorizationIdentity();
assertNotNull("Unexpected null authenticated realm identity", authRealmIdentity);
// check if the authenticated identity returns the caller principal as set by the test login module.
// Principal authPrincipal = authRealmIdentity.getPrincipal();
// assertNotNull("Unexpected null principal", authPrincipal);
// assertEquals("Invalid principal name", new NamePrincipal("auth-caller"), authPrincipal);

// dispose the auth realm identity - should trigger a JAAS logout that clears the subject.
// TODO - some other solution is needed here! We can no longer force JAAS logout in an authorization scenario.
//authPrincipal = authRealmIdentity.getPrincipal();
// after the logout, the subject no longer contains a caller principal so the identity should return the same principal as the realm identity.
//assertNotNull("Unexpected null principal", authPrincipal);
//assertEquals("Invalid principal name", new NamePrincipal("elytron"), authPrincipal);

}

@Test
public void testJaasSecurityRealmWithCustomCallbackHandler() throws Exception {

// create a JAAS realm that takes a custom callback handler.
SecurityRealm realm = new JaasSecurityRealm("test", new TestCallbackHandler());

SecurityRealm realm = new JaasSecurityRealm("Entry1", null, null, new TestCallbackHandler());
// create a new realm identity using the realm.
RealmIdentity realmIdentity = realm.getRealmIdentity(new NamePrincipal("javajoe"));

assertEquals("Invalid credential support", SupportLevel.SUPPORTED, realmIdentity.getEvidenceVerifySupport(PasswordGuessEvidence.class, null));
assertEquals("Invalid credential support", SupportLevel.UNSUPPORTED, realmIdentity.getEvidenceVerifySupport(X509PeerCertificateChainEvidence.class, null));

// verify the credentials using the custom callback handler.
assertTrue(realmIdentity.verifyEvidence(new PasswordGuessEvidence("$#21pass".toCharArray())));
assertFalse(realmIdentity.verifyEvidence(new PasswordGuessEvidence("wrongpass".toCharArray())));
}

@Test
public void testJaasRealmRoles() throws Exception { // is in role
SecurityRealm realm = new JaasSecurityRealm( "Entry1", null, null, new TestCallbackHandler());
SecurityDomain domainWithRewriter = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", realm).build()
.setPermissionMapper(((permissionMappable, roles) -> LoginPermission.getInstance()))
.build();
ServerAuthenticationContext sac1 = domainWithRewriter.createNewAuthenticationContext();
sac1.setAuthenticationPrincipal(new NamePrincipal("javajoe"));
assertTrue(sac1.verifyEvidence(new PasswordGuessEvidence("$#21pass".toCharArray())));
Assert.assertTrue(sac1.authorize());
Assert.assertTrue(sac1.exists());
Assert.assertTrue(sac1.getAuthorizedIdentity().getRoles().contains("Admin"));
Assert.assertTrue(sac1.getAuthorizedIdentity().getRoles().contains("User"));
Assert.assertTrue(sac1.getAuthorizedIdentity().getRoles().contains("Guest"));
Assert.assertFalse(sac1.getAuthorizedIdentity().getRoles().contains("Non_existent_role"));
}

@Test
public void testJaasRealmAttributes() throws Exception {
SecurityRealm realm = new JaasSecurityRealm("Entry1", null,null, new TestCallbackHandler());
SecurityDomain domainWithRewriter = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", realm).build()
.setPermissionMapper(((permissionMappable, roles) -> LoginPermission.getInstance()))
.build();
ServerAuthenticationContext sac1 = domainWithRewriter.createNewAuthenticationContext();
sac1.setAuthenticationPrincipal(new NamePrincipal("javajoe"));
assertTrue(sac1.verifyEvidence(new PasswordGuessEvidence("$#21pass".toCharArray())));
Assert.assertTrue(sac1.authorize());
Assert.assertTrue(sac1.exists());
Assert.assertTrue(sac1.getAuthorizedIdentity().getAttributes().containsKey("NamePrincipal"));
Assert.assertEquals("whoami", sac1.getAuthorizedIdentity().getAttributes().get("NamePrincipal").get(0));
Assert.assertEquals("anonymous", sac1.getAuthorizedIdentity().getAttributes().get("AnonymousPrincipal").get(0));
Assert.assertNotEquals("non_existent_attribute", sac1.getAuthorizedIdentity().getAttributes().get("NamePrincipal").get(0));
Assert.assertNotEquals("whoami", sac1.getAuthorizedIdentity().getAttributes().get("NonExistentAttributeKey").get(0));
Assert.assertEquals("Admin", sac1.getAuthorizedIdentity().getAttributes().get("Roles").get(0));
Assert.assertEquals("User", sac1.getAuthorizedIdentity().getAttributes().get("Roles").get(1));
Assert.assertEquals("Guest", sac1.getAuthorizedIdentity().getAttributes().get("Roles").get(2));
}

@Test
public void testJaasSecurityRealmCredentialsOfUser() throws Exception {
SecurityRealm realm = new JaasSecurityRealm( "Entry1", null, null, new TestCallbackHandler());
SecurityDomain domainWithRewriter = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", realm).build()
.setPermissionMapper(((permissionMappable, roles) -> LoginPermission.getInstance()))
.build();
ServerAuthenticationContext sac1 = domainWithRewriter.createNewAuthenticationContext();
sac1.setAuthenticationPrincipal(new NamePrincipal("javajoe"));
assertTrue(sac1.verifyEvidence(new PasswordGuessEvidence("$#21pass".toCharArray())));
Assert.assertTrue(sac1.authorize());
Assert.assertTrue(sac1.exists());
Assert.assertTrue(sac1.getAuthorizedIdentity().getPrivateCredentials().contains(BearerTokenCredential.class));
Assert.assertTrue(sac1.getAuthorizedIdentity().getPrivateCredentials().contains(PasswordCredential.class));
Assert.assertFalse(sac1.getAuthorizedIdentity().getPrivateCredentials().contains(X509CertificateChainCredential.class));
Assert.assertTrue(sac1.getAuthorizedIdentity().getPublicCredentials().contains(BearerTokenCredential.class));
Assert.assertEquals(sac1.getAuthorizedIdentity().getPublicCredentials().size(), 1);
Assert.assertEquals(sac1.getAuthorizedIdentity().getPrivateCredentials().size(), 2);
Assert.assertEquals(sac1.getAuthorizedIdentity().getPrivateCredentials().getCredential(BearerTokenCredential.class).getToken(),"myPrivateToken");
Assert.assertEquals(sac1.getAuthorizedIdentity().getPublicCredentials().getCredential(BearerTokenCredential.class).getToken(),"myPublicToken");

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@
import javax.security.auth.spi.LoginModule;
import java.io.IOException;
import java.security.Principal;
import java.security.acl.Group;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import org.wildfly.security.auth.principal.AnonymousPrincipal;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.credential.BearerTokenCredential;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.password.interfaces.ClearPassword;

/**
* A {@link javax.security.auth.spi.LoginModule} implementation used in the JAAS security realm tests. It uses a static
Expand All @@ -46,8 +46,7 @@
*/
public class TestLoginModule implements LoginModule {

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

Expand All @@ -73,7 +72,6 @@ public boolean login() throws LoginException {
}

final String username = nameCallback.getName();
this.principal = new NamePrincipal(username);
final char[] password = passwordCallback.getPassword();

char[] storedPassword = this.usersMap.get(username);
Expand All @@ -82,11 +80,14 @@ public boolean login() throws LoginException {

@Override
public boolean commit() throws LoginException {
this.subject.getPrincipals().add(this.principal);
// add a caller principal group for testing purposes.
final Group group = new TestGroup("CallerPrincipal");
group.addMember(new NamePrincipal("auth-caller"));
this.subject.getPrincipals().add(group);
this.subject.getPrincipals().add(new NamePrincipal("whoami"));
this.subject.getPrincipals().add(new AnonymousPrincipal());
this.subject.getPrincipals().add(new Roles("Admin"));
this.subject.getPrincipals().add( new Roles("User"));
this.subject.getPrincipals().add(new Roles("Guest"));
subject.getPrivateCredentials().add(new PasswordCredential(ClearPassword.createRaw(ClearPassword.ALGORITHM_CLEAR, "myPrivatePassword".toCharArray())));
subject.getPrivateCredentials().add(new BearerTokenCredential("myPrivateToken"));
subject.getPublicCredentials().add(new BearerTokenCredential("myPublicToken"));
return true;
}

Expand All @@ -101,42 +102,17 @@ public boolean logout() throws LoginException {
return true;
}

/**
* A {@code Group} implementation used in the tests to store the caller principal.
*/
private class TestGroup implements Group {
private static class Roles implements Principal {

private String name;
private HashSet<Principal> principals;
private final String name;

public TestGroup(final String name) {
Roles(final String name) {
this.name = name;
this.principals = new HashSet<Principal>();
}

@Override
public String getName() {
return this.name;
}

@Override
public boolean addMember(Principal user) {
return this.principals.add(user);
}

@Override
public boolean removeMember(Principal user) {
return this.principals.remove(user);
}

@Override
public boolean isMember(Principal member) {
return this.principals.contains(member);
}

@Override
public Enumeration<? extends Principal> members() {
return Collections.enumeration(this.principals);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
test {
Entry1 {
org.wildfly.security.auth.TestLoginModule required;
};
};

0 comments on commit eebf4fd

Please sign in to comment.