Skip to content

Commit

Permalink
[WFLY-6394] Elytron based implementation of the security SPI.
Browse files Browse the repository at this point in the history
  • Loading branch information
sguilhen committed Jan 19, 2017
1 parent 00aae81 commit f407aec
Show file tree
Hide file tree
Showing 8 changed files with 425 additions and 71 deletions.
4 changes: 4 additions & 0 deletions connector/pom.xml
Expand Up @@ -97,6 +97,10 @@
<groupId>org.jboss.ironjacamar</groupId> <groupId>org.jboss.ironjacamar</groupId>
<artifactId>ironjacamar-validator</artifactId> <artifactId>ironjacamar-validator</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.jboss.spec.javax.security.auth.message</groupId>
<artifactId>jboss-jaspi-api_1.1_spec</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.jboss.logging</groupId> <groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
Expand Down
Expand Up @@ -19,35 +19,56 @@


import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;


import javax.resource.spi.security.PasswordCredential;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.callback.PasswordValidationCallback;


import org.jboss.jca.core.spi.security.Callback; import org.jboss.jca.core.spi.security.Callback;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.auth.server.RealmUnavailableException;
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.RoleMapper;
import org.wildfly.security.authz.Roles;
import org.wildfly.security.evidence.PasswordGuessEvidence;
import org.wildfly.security.manager.WildFlySecurityManager;


/** /**
* CallbackHandler implementation for Elytron * An Elytron based {@link CallbackHandler} implementation designed for the JCA security inflow. It uses the information
* obtained from the {@link javax.security.auth.callback.Callback}s to authenticate and authorize the identity supplied
* by the resource adapter and inserts the {@link SecurityIdentity} representing the authorized identity in the subject's
* private credentials set.
* *
* @author Flavia Rainone * @author Flavia Rainone
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/ */
public class ElytronCallbackHandler implements CallbackHandler, Serializable { public class ElytronCallbackHandler implements CallbackHandler, Serializable {


/** Callback mappings */ private final SecurityDomain securityDomain;
private Callback mappings;


/** /** Callback mappings */
* Constructor private final Callback mappings;
*/
public ElytronCallbackHandler() {
this(null);
}


// TODO initialize the execution subject at the constructor.
private Subject executionSubject;
/** /**
* Constructor * Constructor
* @param mappings The mappings * @param mappings The mappings
*/ */
public ElytronCallbackHandler(Callback mappings) { public ElytronCallbackHandler(final SecurityDomain securityDomain, final Callback mappings) {
this.securityDomain = securityDomain;
this.mappings = mappings; this.mappings = mappings;
} }


Expand All @@ -64,7 +85,164 @@ public void handle(javax.security.auth.callback.Callback[] callbacks) throws Uns
{ {
callbacks = mappings.mapCallbacks(callbacks); callbacks = mappings.mapCallbacks(callbacks);
} }
// TODO
GroupPrincipalCallback groupPrincipalCallback = null;
CallerPrincipalCallback callerPrincipalCallback = null;
PasswordValidationCallback passwordValidationCallback = null;

for (javax.security.auth.callback.Callback callback : callbacks) {
if (callback instanceof GroupPrincipalCallback) {
groupPrincipalCallback = (GroupPrincipalCallback) callback;
if (executionSubject == null) {
executionSubject = groupPrincipalCallback.getSubject();
} else if (!executionSubject.equals(groupPrincipalCallback.getSubject())) {
// TODO merge the contents of the subjects?
}
} else if (callback instanceof CallerPrincipalCallback) {
callerPrincipalCallback = (CallerPrincipalCallback) callback;
if (executionSubject == null) {
executionSubject = callerPrincipalCallback.getSubject();
} else if (!executionSubject.equals(callerPrincipalCallback.getSubject())) {
// TODO merge the contents of the subjects?
}
} else if (callback instanceof PasswordValidationCallback) {
passwordValidationCallback = (PasswordValidationCallback) callback;
if (executionSubject == null) {
executionSubject = passwordValidationCallback.getSubject();
} else if (!executionSubject.equals(passwordValidationCallback.getSubject())) {
// TODO merge the contents of the subjects?
}
} else {
throw new UnsupportedCallbackException(callback);
}
}
this.processResults(callerPrincipalCallback, groupPrincipalCallback, passwordValidationCallback);
}
}

protected void processResults(final CallerPrincipalCallback callerPrincipalCallback, final GroupPrincipalCallback groupPrincipalCallback,
final PasswordValidationCallback passwordValidationCallback) throws IOException {//, UnsupportedCallbackException {

// spec section 16.4.5 - no CallerPrincipalCallback was handled, check the execution subject's principal set.
SecurityIdentity authenticatedIdentity = this.securityDomain.getAnonymousSecurityIdentity();
Principal callerPrincipal = null;
if (callerPrincipalCallback == null) {
if (executionSubject.getPrincipals().size() == 1) {
Principal subjectPrincipal = executionSubject.getPrincipals().iterator().next();
callerPrincipal = new NamePrincipal(subjectPrincipal.getName());
// TODO apply mapping to the principal if needed
} else if (!executionSubject.getPrincipals().isEmpty()) {
// TODO throw exception here (spec violation)?
}
} else {
Principal callbackPrincipal = callerPrincipalCallback.getPrincipal();
callerPrincipal = callbackPrincipal != null ? new NamePrincipal(callbackPrincipal.getName()) :
callerPrincipalCallback.getName() != null ? new NamePrincipal(callerPrincipalCallback.getName()) : null;
}

// a null principal is the ra contract for requiring the use of the unauthenticated identity - there's no point trying to authenticate any identity.
if (callerPrincipal != null) {
// check if we have a username/password pair to authenticate - first try the password validation callback.
if (passwordValidationCallback != null) {
authenticatedIdentity = this.authenticate(passwordValidationCallback.getUsername(), passwordValidationCallback.getPassword());
} else {
// identity not established using the callback - check if the execution subject contains a password credential.
PasswordCredential passwordCredential = this.getPasswordCredential(this.executionSubject);
if (passwordCredential != null) {
authenticatedIdentity = this.authenticate(passwordCredential.getUserName(), passwordCredential.getPassword());
}
}
}

// if the caller principal is different from the authenticated identity principal, switch to the caller principal identity.
if (callerPrincipal != null && !callerPrincipal.equals(authenticatedIdentity.getPrincipal())) {
authenticatedIdentity = authenticatedIdentity.createRunAsIdentity(callerPrincipal.getName());
}

// if we have new roles coming from the group callback, set a new mapper in the identity.
if (groupPrincipalCallback != null) {
String[] groups = groupPrincipalCallback.getGroups();
if (groups != null) {
Set<String> roles = new HashSet<>(Arrays.asList(groups));
// TODO what category should we use here? Is it right to assume every entry in the groups array represents a role?
authenticatedIdentity = authenticatedIdentity.withRoleMapper("jca", RoleMapper.constant(Roles.fromSet(roles)));
}
}

// set the authenticated identity as a private credential in the subject.
if (executionSubject != null) {
this.addPrivateCredential(executionSubject, authenticatedIdentity);
}
}

/**
* Authenticate the user with the given credential against the configured Elytron security domain.
*
* @param username the user being authenticated.
* @param credential the credential used as evidence to verify the user's identity.
* @return the authenticated and authorized {@link SecurityIdentity}.
* @throws IOException if an error occurs while authenticating the user.
*/
private SecurityIdentity authenticate(final String username, final char[] credential) throws IOException {
final ServerAuthenticationContext context = this.securityDomain.createNewAuthenticationContext();
final PasswordGuessEvidence evidence = new PasswordGuessEvidence(credential != null ? credential : null);
try {
context.setAuthenticationName(username);
if (context.verifyEvidence(evidence)) {
if (context.authorize()) {
context.succeed();
return context.getAuthorizedIdentity();
} else {
context.fail();
throw new SecurityException("Authorization failed");
}
} else {
context.fail();
throw new SecurityException("Authentication failed");
}
} catch (IllegalArgumentException | IllegalStateException | RealmUnavailableException e) {
context.fail();
throw e;
} finally {
if (!context.isDone()) {
context.fail();
}
evidence.destroy();
}
}

protected PasswordCredential getPasswordCredential(final Subject subject) {
PasswordCredential credential = null;
if (subject != null) {
Set<PasswordCredential> credentialSet;
if (!WildFlySecurityManager.isChecking()) {
credentialSet = subject.getPrivateCredentials(PasswordCredential.class);
} else {
credentialSet = AccessController.doPrivileged((PrivilegedAction<Set<PasswordCredential>>) () ->
subject.getPrivateCredentials(PasswordCredential.class));
}
if (!credentialSet.isEmpty()) {
credential = credentialSet.iterator().next();
}
}
return credential;
}

/**
* Add the specified credential to the subject's private credentials set.
*
* @param subject the {@link Subject} to add the credential to.
* @param credential a reference to the credential.
*/
protected void addPrivateCredential(final Subject subject, final Object credential) {
if (!WildFlySecurityManager.isChecking()) {
subject.getPrivateCredentials().add(credential);
}
else {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
subject.getPrivateCredentials().add(credential);
return null;
});
} }
} }


Expand Down
Expand Up @@ -15,43 +15,52 @@
*/ */
package org.jboss.as.connector.security; package org.jboss.as.connector.security;


import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Set;

import javax.security.auth.Subject; import javax.security.auth.Subject;


import org.jboss.jca.core.spi.security.SecurityContext; import org.jboss.jca.core.spi.security.SecurityContext;
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.manager.WildFlySecurityManager;


/** /**
* SecurityContext implementation for Elytron. * An Elytron based {@link SecurityContext} implementation.
* *
* @author Flavia Rainone * @author Flavia Rainone
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/ */
public class ElytronSecurityContext implements SecurityContext { public class ElytronSecurityContext implements SecurityContext {


/** private Subject authenticatedSubject;
* Constructor
*/
public ElytronSecurityContext() {
}


/** @Override
* {@inheritDoc}
*/
public Subject getAuthenticatedSubject() { public Subject getAuthenticatedSubject() {
// TODO return this.authenticatedSubject;
return null;
} }


/** @Override
* {@inheritDoc} public void setAuthenticatedSubject(final Subject subject) {
*/ this.authenticatedSubject = subject;
public void setAuthenticatedSubject(Subject subject) {
// TODO
} }


/** @Override
* {@inheritDoc}
*/
public String[] getRoles() { public String[] getRoles() {
return null; // TODO if (this.authenticatedSubject != null) {
// check if the authenticated subject contains a SecurityIdentity in its private credentials.
Set<SecurityIdentity> authenticatedIdentities = this.getPrivateCredentials(SecurityIdentity.class);
// iterate through the identities adding all the roles found.
final Set<String> rolesSet = new HashSet<>();
for (SecurityIdentity identity : authenticatedIdentities) {
for (String role : identity.getRoles()) {
rolesSet.add(role);
}
}
return rolesSet.toArray(new String[rolesSet.size()]);
}
return new String[0];
} }


/** /**
Expand All @@ -60,6 +69,25 @@ public String[] getRoles() {
* @param work executes the work * @param work executes the work
*/ */
public void runWork(Runnable work) { public void runWork(Runnable work) {
// TODO // if we have an authenticated subject we check if it contains a security identity and use the identity to run the work.
if (this.authenticatedSubject != null) {
Set<SecurityIdentity> authenticatedIdentities = this.getPrivateCredentials(SecurityIdentity.class);
if (!authenticatedIdentities.isEmpty()) {
SecurityIdentity identity = authenticatedIdentities.iterator().next();
identity.runAs(work);
return;
}
}
// no authenticated subject found or the subject didn't have a security identity - just run the work.
work.run();
} }

protected<T> Set<T> getPrivateCredentials(Class<T> credentialClass) {
if (!WildFlySecurityManager.isChecking()) {
return this.authenticatedSubject.getPrivateCredentials(credentialClass);
} else {
return AccessController.doPrivileged((PrivilegedAction<Set<T>>) () -> this.authenticatedSubject.getPrivateCredentials(credentialClass));
}
}

} }

0 comments on commit f407aec

Please sign in to comment.