/* * JBoss, Home of Professional Open Source. * Copyright 2016 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.wildfly.security.sasl.util; import static org.wildfly.common.Assert.checkNotNullParam; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; 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.SaslException; import javax.security.sasl.SaslServer; import javax.security.sasl.SaslServerFactory; import org.wildfly.security._private.ElytronMessages; import org.wildfly.security.auth.callback.AvailableRealmsCallback; import org.wildfly.security.sasl.WildFlySasl; import org.wildfly.security.util.CodePointIterator; /** * A {@link SaslServerFactory} which uses the {@link AvailableRealmsCallback} to populate the legacy * {@link WildFlySasl#REALM_LIST} property, if needed by a mechanism. * * @author <a href="mailto:fjuma@redhat.com">Farah Juma</a> */ public final class LegacyRealmListSaslServerFactory extends AbstractDelegatingSaslServerFactory { public static final char DEFAULT_DELIMITER = ' '; public static final char DEFAULT_ESCAPE_CHARACTER = '\\'; private final int escapeCharacter; private final int[] delims; /** * Construct a new instance. The delimiter that should be used to separate the realm names when populating the * list of realms is assumed to be {@value #DEFAULT_DELIMITER}. The escape character is assumed to be * {@value #DEFAULT_ESCAPE_CHARACTER}. * * @param delegate the delegate {@code SaslServerFactory} */ public LegacyRealmListSaslServerFactory(final SaslServerFactory delegate) { this(delegate, DEFAULT_ESCAPE_CHARACTER, DEFAULT_DELIMITER); } /** * Construct a new instance. * * @param delegate the delegate {@code SaslServerFactory} * @param escapeCharacter the escape character to use when populating the list of realms * @param delims the delimiters that should be used to separate the realm names when populating the list of realms */ public LegacyRealmListSaslServerFactory(final SaslServerFactory delegate, final char escapeCharacter, final String delims) { super(delegate); checkNotNullParam("escapeCharacter", escapeCharacter); checkNotNullParam("delims", delims); this.escapeCharacter = escapeCharacter; this.delims = delims.chars().toArray(); } /** * Construct a new instance. * * @param delegate the delegate {@code SaslServerFactory} * @param escapeCharacter the escape character to use when populating the list of realms * @param delims the delimiters that should be used to separate the realm names when populating the list of realms */ public LegacyRealmListSaslServerFactory(final SaslServerFactory delegate, final char escapeCharacter, final int... delims) { super(delegate); this.escapeCharacter = checkNotNullParam("escapeCharacter", escapeCharacter); this.delims = checkNotNullParam("delims", delims); } public SaslServer createSaslServer(final String mechanism, final String protocol, final String serverName, final Map<String, ?> props, final CallbackHandler cbh) throws SaslException { String[] realms = null; final AvailableRealmsCallback availableRealmsCallback = new AvailableRealmsCallback(); try { cbh.handle(new Callback[] { availableRealmsCallback }); realms = availableRealmsCallback.getRealmNames(); } catch (UnsupportedCallbackException ignored) { } catch (SaslException e) { throw e; } catch (IOException e) { throw ElytronMessages.log.mechCallbackHandlerFailedForUnknownReason(mechanism, e).toSaslException(); } if (realms == null) { realms = new String[] { serverName }; } final String realmList = arrayToRealmListProperty(realms, escapeCharacter, delims); final Map<String, Object> newProps = new HashMap<String, Object>(props) { public Object get(Object key) { Object value = super.get(key); if (key.equals(WildFlySasl.REALM_LIST) && (value == null)) { value = realmList; put(WildFlySasl.REALM_LIST, value); } return value; } }; return delegate.createSaslServer(mechanism, protocol, serverName, newProps, cbh); } static String arrayToRealmListProperty(String[] realms) { return arrayToRealmListProperty(realms, DEFAULT_ESCAPE_CHARACTER, DEFAULT_DELIMITER); } static String arrayToRealmListProperty(String[] realms, int escapeCharacter, int... delims) { if (realms == null) { return null; } final int[] escapeCharacterAndDelims = Arrays.copyOf(delims, delims.length + 1); escapeCharacterAndDelims[escapeCharacterAndDelims.length - 1] = escapeCharacter; StringBuilder realmList = new StringBuilder(); for (int i = 0; i < realms.length; i++) { if (i != 0) { addDelims(realmList, delims); } CodePointIterator cpi = CodePointIterator.ofString(realms[i]); CodePointIterator di = cpi.delimitedBy(escapeCharacterAndDelims); while (cpi.hasNext()) { if (di.hasNext()) { di.drainTo(realmList); } else { realmList.append((char) escapeCharacter); // escape the delimiter or escape character realmList.append((char) cpi.next()); } } } return realmList.toString(); } private static void addDelims(StringBuilder realmList, int... delims) { for (int delim : delims) { realmList.append((char) delim); } } }