package org.infinispan.server.jgroups.security; import java.io.IOException; import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.sasl.AuthorizeCallback; import org.infinispan.server.jgroups.logging.JGroupsLogger; import org.jboss.as.core.security.RealmUser; import org.jboss.as.core.security.SubjectUserInfo; import org.jboss.as.domain.management.AuthMechanism; import org.jboss.as.domain.management.AuthorizingCallbackHandler; import org.jboss.as.domain.management.RealmConfigurationConstants; import org.jboss.as.domain.management.SecurityRealm; /** * RealmAuthorizationCallbackHandler. A {@link CallbackHandler} for JGroups which piggybacks on the * realm-provided {@link AuthorizingCallbackHandler}s and provides additional role validation * * @author Tristan Tarrant * @since 7.0 */ public class RealmAuthorizationCallbackHandler implements CallbackHandler { private final String mechanismName; private final SecurityRealm realm; private final String clusterRole; static final String SASL_OPT_REALM_PROPERTY = "com.sun.security.sasl.digest.realm"; static final String SASL_OPT_PRE_DIGESTED_PROPERTY = "org.jboss.sasl.digest.pre_digested"; static final String DIGEST_MD5 = "DIGEST-MD5"; static final String EXTERNAL = "EXTERNAL"; static final String GSSAPI = "GSSAPI"; static final String PLAIN = "PLAIN"; public RealmAuthorizationCallbackHandler(SecurityRealm realm, String mechanismName, String clusterRole, Map<String, String> mechanismProperties) { this.realm = realm; this.mechanismName = mechanismName; this.clusterRole = clusterRole; tunePropsForMech(mechanismProperties); } private void tunePropsForMech(Map<String, String> mechanismProperties) { if (DIGEST_MD5.equals(mechanismName)) { if (!mechanismProperties.containsKey(SASL_OPT_REALM_PROPERTY)) { mechanismProperties.put(SASL_OPT_REALM_PROPERTY, realm.getName()); } Map<String, String> mechConfig = realm.getMechanismConfig(AuthMechanism.DIGEST); boolean plainTextDigest = true; if (mechConfig.containsKey(RealmConfigurationConstants.DIGEST_PLAIN_TEXT)) { plainTextDigest = Boolean.parseBoolean(mechConfig.get(RealmConfigurationConstants.DIGEST_PLAIN_TEXT)); } if (!plainTextDigest) { mechanismProperties.put(SASL_OPT_PRE_DIGESTED_PROPERTY, "true"); } } } @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { AuthorizingCallbackHandler cbh = getMechCallbackHandler(); cbh.handle(callbacks); } private AuthorizingCallbackHandler getMechCallbackHandler() { if (PLAIN.equals(mechanismName)) { return new DelegatingRoleAwareAuthorizingCallbackHandler(realm.getAuthorizingCallbackHandler(AuthMechanism.PLAIN)); } else if (DIGEST_MD5.equals(mechanismName)) { return new DelegatingRoleAwareAuthorizingCallbackHandler(realm.getAuthorizingCallbackHandler(AuthMechanism.DIGEST)); } else if (GSSAPI.equals(mechanismName)) { return new DelegatingRoleAwareAuthorizingCallbackHandler(realm.getAuthorizingCallbackHandler(AuthMechanism.PLAIN)); } else if (EXTERNAL.equals(mechanismName)) { return new DelegatingRoleAwareAuthorizingCallbackHandler(realm.getAuthorizingCallbackHandler(AuthMechanism.CLIENT_CERT)); } else { throw new IllegalArgumentException("Unsupported mech " + mechanismName); } } SubjectUserInfo validateSubjectRole(SubjectUserInfo subjectUserInfo) { for(Principal principal : subjectUserInfo.getPrincipals()) { if (clusterRole.equals(principal.getName())) { return subjectUserInfo; } } throw JGroupsLogger.ROOT_LOGGER.unauthorizedNodeJoin(subjectUserInfo.getUserName()); } class DelegatingRoleAwareAuthorizingCallbackHandler implements AuthorizingCallbackHandler { private final AuthorizingCallbackHandler delegate; DelegatingRoleAwareAuthorizingCallbackHandler(AuthorizingCallbackHandler acbh) { this.delegate = acbh; } @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { AuthorizeCallback acb = findCallbackHandler(AuthorizeCallback.class, callbacks); if (acb != null) { String authenticationId = acb.getAuthenticationID(); String authorizationId = acb.getAuthorizationID(); acb.setAuthorized(authenticationId.equals(authorizationId)); int realmSep = authorizationId.indexOf('@'); RealmUser realmUser = realmSep < 0 ? new RealmUser(authorizationId) : new RealmUser(authorizationId.substring(realmSep+1), authorizationId.substring(0, realmSep)); List<Principal> principals = new ArrayList<>(); principals.add(realmUser); createSubjectUserInfo(principals); } else { delegate.handle(callbacks); } } @Override public SubjectUserInfo createSubjectUserInfo(Collection<Principal> principals) throws IOException { // The call to the delegate will supplement the user with additional role information SubjectUserInfo subjectUserInfo = delegate.createSubjectUserInfo(principals); return validateSubjectRole(subjectUserInfo); } } public static <T extends Callback> T findCallbackHandler(Class<T> klass, Callback[] callbacks) { for(int i=0; i < callbacks.length; i++) { if (klass.isInstance(callbacks[i])) { return (T) callbacks[i]; } } return null; } }