All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.security.auth.server.SecurityDomain Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

The newest version!
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013 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.auth.server;

import static java.security.AccessController.doPrivileged;
import static java.util.Collections.emptyMap;
import static org.wildfly.common.Assert.checkNotNullParam;
import static org.wildfly.security.auth.server._private.ElytronMessages.log;

import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

import org.wildfly.common.Assert;
import org.wildfly.common.function.ExceptionBiFunction;
import org.wildfly.common.function.ExceptionFunction;
import org.wildfly.security.auth.SupportLevel;
import org.wildfly.security.auth.principal.AnonymousPrincipal;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.auth.principal.RealmNestedPrincipal;
import org.wildfly.security.auth.server.event.SecurityEvent;
import org.wildfly.security.authz.AuthorizationIdentity;
import org.wildfly.security.authz.PermissionMapper;
import org.wildfly.security.authz.RoleDecoder;
import org.wildfly.security.authz.RoleMapper;
import org.wildfly.security.authz.Roles;
import org.wildfly.security.credential.BearerTokenCredential;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.evidence.BearerTokenEvidence;
import org.wildfly.security.evidence.Evidence;
import org.wildfly.security.evidence.PasswordGuessEvidence;
import org.wildfly.security.password.interfaces.ClearPassword;
import org.wildfly.security.permission.ElytronPermission;
import org.wildfly.security.permission.PermissionVerifier;

/**
 * A security domain.  Security domains encapsulate a set of security policies.
 *
 * @author David M. Lloyd
 * @author Darran Lofthouse
 */
public final class SecurityDomain {

    private static final ConcurrentHashMap CLASS_LOADER_DOMAIN_MAP = new ConcurrentHashMap<>();
    private static final RealmInfo EMPTY_REALM_INFO = new RealmInfo();

    static final ElytronPermission AUTHENTICATE = ElytronPermission.forName("authenticate");
    static final ElytronPermission CREATE_SECURITY_DOMAIN = ElytronPermission.forName("createSecurityDomain");
    static final ElytronPermission REGISTER_SECURITY_DOMAIN = ElytronPermission.forName("registerSecurityDomain");
    static final ElytronPermission GET_SECURITY_DOMAIN = ElytronPermission.forName("getSecurityDomain");
    static final ElytronPermission UNREGISTER_SECURITY_DOMAIN = ElytronPermission.forName("unregisterSecurityDomain");
    static final ElytronPermission CREATE_AUTH_CONTEXT = ElytronPermission.forName("createServerAuthenticationContext");
    static final ElytronPermission GET_IDENTITY = ElytronPermission.forName("getIdentity");
    static final ElytronPermission GET_IDENTITY_FOR_UPDATE = ElytronPermission.forName("getIdentityForUpdate");
    static final ElytronPermission CREATE_AD_HOC_IDENTITY = ElytronPermission.forName("createAdHocIdentity");
    static final ElytronPermission HANDLE_SECURITY_EVENT = ElytronPermission.forName("handleSecurityEvent");

    private final Map realmMap;
    private final String defaultRealmName;
    private final Function preRealmPrincipalRewriter;
    private final RealmMapper realmMapper;
    private final Function postRealmPrincipalRewriter;
    private final ThreadLocal> currentSecurityIdentity;
    private final RoleMapper roleMapper;
    private final SecurityIdentity anonymousIdentity;
    private final PermissionMapper permissionMapper;
    private final Map categoryRoleMappers;
    private final UnaryOperator securityIdentityTransformer;
    private final Predicate trustedSecurityDomain;
    private final Consumer securityEventListener;
    private final Function evidenceDecoder;
    private final RoleDecoder roleDecoder;

    SecurityDomain(Builder builder, final LinkedHashMap realmMap) {
        this.realmMap = realmMap;
        this.defaultRealmName = builder.defaultRealmName;
        this.preRealmPrincipalRewriter = builder.principalDecoder.andThen(builder.preRealmRewriter);
        this.realmMapper = builder.realmMapper;
        this.roleMapper = builder.roleMapper;
        this.permissionMapper = builder.permissionMapper;
        this.postRealmPrincipalRewriter = builder.postRealmRewriter;
        this.securityIdentityTransformer = builder.securityIdentityTransformer;
        this.trustedSecurityDomain = builder.trustedSecurityDomain;
        this.securityEventListener = builder.securityEventListener;
        this.evidenceDecoder = builder.evidenceDecoder;
        this.roleDecoder = builder.roleDecoder;
        final Map originalRoleMappers = builder.categoryRoleMappers;
        final Map copiedRoleMappers;
        if (originalRoleMappers.isEmpty()) {
            copiedRoleMappers = emptyMap();
        } else if (originalRoleMappers.size() == 1) {
            final Map.Entry entry = originalRoleMappers.entrySet().iterator().next();
            copiedRoleMappers = Collections.singletonMap(entry.getKey(), entry.getValue());
        } else {
            copiedRoleMappers = new LinkedHashMap<>(originalRoleMappers);
        }
        this.categoryRoleMappers = copiedRoleMappers;
        // todo configurable
        anonymousIdentity = Assert.assertNotNull(securityIdentityTransformer.apply(new SecurityIdentity(this, AnonymousPrincipal.getInstance(), EMPTY_REALM_INFO, AuthorizationIdentity.EMPTY, copiedRoleMappers, IdentityCredentials.NONE, IdentityCredentials.NONE)));
        currentSecurityIdentity = ThreadLocal.withInitial(() -> anonymousIdentity);
    }

    /**
     * Register this {@link SecurityDomain} with the specified {@link ClassLoader}.
     *
     * Registration with enabled security manager requires {@code registerSecurityDomain} {@link ElytronPermission}.
     *
     * @throws IllegalStateException If a {@link SecurityDomain} is already associated with the specified {@link ClassLoader}.
     * @param classLoader the non {@code null} {@link ClassLoader} to associate this {@link SecurityDomain} with.
     */
    public void registerWithClassLoader(ClassLoader classLoader) {
        checkNotNullParam("classLoader", classLoader);
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(REGISTER_SECURITY_DOMAIN);
        }

        final SecurityDomain classLoaderDomain = CLASS_LOADER_DOMAIN_MAP.putIfAbsent(classLoader, this);
        if ((classLoaderDomain != null) && (classLoaderDomain != this)) {
            throw log.classLoaderSecurityDomainExists();
        }
    }

    /**
     * Get the {@link SecurityDomain} associated with the context class loader of the calling Thread or {@code null} if one is
     * not associated.
     *
     * Obtaining security domain with enabled security manager requires {@code getSecurityDomain} {@link ElytronPermission}.
     *
     * @return the {@link SecurityDomain} associated with the context class loader of the calling Thread or {@code null} if one
     *         is not associated.
     */
    public static SecurityDomain getCurrent() {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(GET_SECURITY_DOMAIN);
        }

        final Thread currentThread = Thread.currentThread();
        ClassLoader classLoader;
        if (sm != null) {
            classLoader = doPrivileged((PrivilegedAction) currentThread::getContextClassLoader);
        } else {
            classLoader = currentThread.getContextClassLoader();
        }

        return classLoader != null ? CLASS_LOADER_DOMAIN_MAP.get(classLoader) : null;
    }

    /**
     * Get the security domain associated with the given identity.
     *
     * Obtaining security domain with enabled security manager requires {@code getSecurityDomain} {@link ElytronPermission}.
     *
     * @param identity the security identity (must not be {@code null})
     * @return the identity's security domain (not {@code null})
     */
    public static SecurityDomain forIdentity(SecurityIdentity identity) {
        checkNotNullParam("identity", identity);
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(GET_SECURITY_DOMAIN);
        }
        return identity.getSecurityDomain();
    }

    /**
     * Unregister any {@link SecurityDomain} associated with the specified {@link ClassLoader}.
     *
     * Unregistration with enabled security manager requires {@code unregisterSecurityDomain} {@link ElytronPermission}.
     *
     * @param classLoader the non {@code null} {@link ClassLoader} to clear any {@link SecurityDomain} association.
     */
    public static void unregisterClassLoader(ClassLoader classLoader) {
        checkNotNullParam("classLoader", classLoader);
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(UNREGISTER_SECURITY_DOMAIN);
        }

        CLASS_LOADER_DOMAIN_MAP.remove(classLoader);
    }

    /**
     * Create a new security domain builder.
     *
     * @return the builder
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * Create a new authentication context for this security domain which can be used to carry out a single authentication
     * operation.
     *
     * Calling with enabled security manager requires {@code createServerAuthenticationContext} {@link ElytronPermission}.
     *
     * @return the new authentication context
     */
    public ServerAuthenticationContext createNewAuthenticationContext() {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(CREATE_AUTH_CONTEXT);
        }
        return new ServerAuthenticationContext(this, MechanismConfigurationSelector.constantSelector(MechanismConfiguration.EMPTY));
    }

    /**
     * Create a new authentication context for this security domain which can be used to carry out a single authentication
     * operation.
     *
     * Calling with enabled security manager requires {@code createServerAuthenticationContext} {@link ElytronPermission}.
     *
     * @param mechanismConfigurationSelector the selector to use to obtain the mechanism configuration
     * @return the new authentication context
     */
    public ServerAuthenticationContext createNewAuthenticationContext(MechanismConfigurationSelector mechanismConfigurationSelector) {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(CREATE_AUTH_CONTEXT);
        }
        return new ServerAuthenticationContext(this, mechanismConfigurationSelector);
    }

    ServerAuthenticationContext createNewAuthenticationContext(SecurityIdentity capturedIdentity, MechanismConfigurationSelector mechanismConfigurationSelector) {
        assert capturedIdentity.getSecurityDomain() == this;
        return new ServerAuthenticationContext(capturedIdentity, mechanismConfigurationSelector);
    }

    /**
     * Perform an authentication based on {@link Evidence} alone.
     *
     * Note:  It is the caller's responsibility to destroy any evidence passed into this method.
     *
     * @param evidence the {@link Evidence} to use for authentication.
     * @return the authenticated identity.
     * @throws RealmUnavailableException if the requires {@link SecurityRealm} is not available.
     * @throws SecurityException if authentication fails.
     */
    public SecurityIdentity authenticate(Evidence evidence) throws RealmUnavailableException, SecurityException {
        return authenticate((Principal) null, evidence);
    }

    /**
     * Perform an authentication based on {@link Evidence} for the specified identity name.
     *
     * Note:  It is the caller's responsibility to destroy any evidence passed into this method.
     *
     * @param name the name of the identity to authenticate or {@code null} if the identity is to be derived from the evidence.
     * @param evidence the {@link Evidence} to use for authentication.
     * @return the authenticated identity.
     * @throws RealmUnavailableException if the requires {@link SecurityRealm} is not available.
     * @throws SecurityException if authentication fails.
     */
    public SecurityIdentity authenticate(String name, Evidence evidence) throws RealmUnavailableException, SecurityException {
        return authenticate(name != null ? new NamePrincipal(name) : null, evidence);
    }

    /**
     * Perform an authentication based on {@link Evidence} for the specified identity {@link Principal}.
     *
     * Note:  It is the caller's responsibility to destroy any evidence passed into this method.
     *
     * Calling with enabled security manager requires {@code authenticate} {@link ElytronPermission}.
     *
     * @param principal the principal of the identity to authenticate or {@code null} if the identity is to be derived from the evidence.
     * @param evidence the {@link Evidence} to use for authentication.
     * @return the authenticated identity.
     * @throws RealmUnavailableException if the requires {@link SecurityRealm} is not available.
     * @throws SecurityException if authentication fails.
     */
    public SecurityIdentity authenticate(Principal principal, Evidence evidence) throws RealmUnavailableException, SecurityException {
        final SecurityManager securityManager = System.getSecurityManager();
        if (securityManager != null) {
            securityManager.checkPermission(AUTHENTICATE);
        }

        try (final ServerAuthenticationContext serverAuthenticationContext = new ServerAuthenticationContext(this,
                MechanismConfigurationSelector.constantSelector(MechanismConfiguration.EMPTY))) {
            if (principal != null)
                serverAuthenticationContext.setAuthenticationPrincipal(principal);
            if (serverAuthenticationContext.verifyEvidence(evidence)) {
                if (serverAuthenticationContext.authorize()) {
                    if (evidence instanceof PasswordGuessEvidence) {
                        PasswordGuessEvidence passwordGuessEvidence = PasswordGuessEvidence.class.cast(evidence);
                        serverAuthenticationContext.addPrivateCredential(new PasswordCredential(
                                ClearPassword.createRaw(ClearPassword.ALGORITHM_CLEAR, passwordGuessEvidence.getGuess())));
                    } else if (evidence instanceof BearerTokenEvidence) {
                        BearerTokenEvidence tokenEvidence = BearerTokenEvidence.class.cast(evidence);
                        serverAuthenticationContext.addPrivateCredential(new BearerTokenCredential(tokenEvidence.getToken()));
                    } else {
                        log.tracef(
                                "Evidence [%s] does not map to a supported credential type. Credentials are not available from authorized identity and identity propagation may not work",
                                evidence.getClass().getName());
                    }
                    serverAuthenticationContext.succeed();
                    return serverAuthenticationContext.getAuthorizedIdentity();
                } else {
                    serverAuthenticationContext.fail();
                    throw log.authenticationFailedAuthorization();
                }
            } else {
                serverAuthenticationContext.fail();
                throw log.authenticationFailedEvidenceVerification();
            }
        }
    }

    /**
     * Look up a {@link RealmIdentity} by name by wrapping the name in a {@link NamePrincipal} and calling {@link #getIdentity(Principal)}.
     * The returned identity must be {@linkplain RealmIdentity#dispose() disposed}.
     *
     * @param name the name to map (must not be {@code null})
     * @return the identity for the name (not {@code null}, may be non-existent)
     * @throws RealmUnavailableException if the realm is not able to perform the mapping
     * @throws IllegalArgumentException if the name is not valid
     * @throws SecurityException if the caller is not authorized to perform the operation
     */
    public RealmIdentity getIdentity(String name) throws RealmUnavailableException {
        Assert.checkNotNullParam("name", name);
        return getIdentity(new NamePrincipal(name));
    }

    /**
     * Look up a {@link RealmIdentity} by principal.
     * The returned identity must be {@linkplain RealmIdentity#dispose() disposed}.
     *
     * Calling with enabled security manager requires {@code getIdentity} {@link ElytronPermission}.
     *
     * @param principal the principal to map (must not be {@code null})
     * @return the identity for the name (not {@code null}, may be non-existent)
     * @throws IllegalArgumentException if the principal could not be successfully decoded to a name
     * @throws RealmUnavailableException if the realm is not able to perform the mapping
     * @throws SecurityException if the caller is not authorized to perform the operation
     */
    public RealmIdentity getIdentity(Principal principal) throws RealmUnavailableException, IllegalArgumentException {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(GET_IDENTITY);
        }
        return getIdentityPrivileged(principal, SecurityRealm.class, SecurityRealm::getRealmIdentity, () -> RealmIdentity.NON_EXISTENT, () -> RealmIdentity.ANONYMOUS);
    }

    /**
     * Look up a {@link ModifiableRealmIdentity} by principal.
     * The returned identity must be {@linkplain RealmIdentity#dispose() disposed}.
     *
     * Calling with enabled security manager requires {@code getIdentityForUpdate} {@link ElytronPermission}.
     *
     * @param principal the principal to map (must not be {@code null})
     * @return the identity for the name (not {@code null}, may be non-existent)
     * @throws IllegalArgumentException if the principal could not be successfully decoded to a name
     * @throws RealmUnavailableException if the realm is not able to perform the mapping
     * @throws SecurityException if the caller is not authorized to perform the operation
     */
    public ModifiableRealmIdentity getIdentityForUpdate(Principal principal) throws RealmUnavailableException, IllegalArgumentException {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(GET_IDENTITY_FOR_UPDATE);
        }
        return getIdentityPrivileged(principal, ModifiableSecurityRealm.class, ModifiableSecurityRealm::getRealmIdentityForUpdate, () -> ModifiableRealmIdentity.NON_EXISTENT, () -> ModifiableRealmIdentity.NON_EXISTENT);
    }

    /**
     * Get a function which can be used to look up principals without a security manager permission check.
     * All returned identities must be {@linkplain RealmIdentity#dispose() disposed}.
     *
     * Calling with enabled security manager requires {@code getIdentity} {@link ElytronPermission}.
     *
     * @return the lookup function (not {@code null})
     * @throws SecurityException if the caller is not authorized to perform the operation
     */
    public ExceptionFunction getIdentityLookupFunction() {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(GET_IDENTITY);
        }
        return p -> getIdentityPrivileged(p, SecurityRealm.class, SecurityRealm::getRealmIdentity, () -> RealmIdentity.NON_EXISTENT, () -> RealmIdentity.ANONYMOUS);
    }

    /**
     * Get a function which can be used to look up principals for update without a security manager permission check.
     * All returned identities must be {@linkplain RealmIdentity#dispose() disposed}.
     * Calling with enabled security manager requires {@code getIdentityForUpdate} {@link ElytronPermission}.
     *
     * @return the lookup function (not {@code null})
     * @throws SecurityException if the caller is not authorized to perform the operation
     */
    public ExceptionFunction getIdentityLookupForUpdateFunction() {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(GET_IDENTITY_FOR_UPDATE);
        }
        return p -> getIdentityPrivileged(p, ModifiableSecurityRealm.class, ModifiableSecurityRealm::getRealmIdentityForUpdate, () -> ModifiableRealmIdentity.NON_EXISTENT, () -> ModifiableRealmIdentity.NON_EXISTENT);
    }

     I getIdentityPrivileged(Principal principal, Class realmType, ExceptionBiFunction fn, Supplier nonExistent, Supplier anonymous) throws RealmUnavailableException {
        Assert.checkNotNullParam("principal", principal);
        if (principal instanceof AnonymousPrincipal) {
            return anonymous.get();
        }
        if (principal instanceof RealmNestedPrincipal) {
            final RealmNestedPrincipal realmNestedPrincipal = (RealmNestedPrincipal) principal;
            final SecurityRealm securityRealm = getRealmInfo(realmNestedPrincipal.getRealmName()).getSecurityRealm();
            if (realmType.isInstance(securityRealm)) {
                return fn.apply(realmType.cast(securityRealm), realmNestedPrincipal.getNestedPrincipal());
            } else {
                return nonExistent.get();
            }
        }
        Principal preRealmPrincipal = preRealmPrincipalRewriter.apply(principal);
        if (preRealmPrincipal == null) {
            throw log.invalidName();
        }

        String realmName = mapRealmName(preRealmPrincipal, null);
        RealmInfo realmInfo = getRealmInfo(realmName);
        SecurityRealm securityRealm = realmInfo.getSecurityRealm();
        assert securityRealm != null;

        Principal postRealmPrincipal = postRealmPrincipalRewriter.apply(preRealmPrincipal);
        if (postRealmPrincipal == null) {
            throw log.invalidName();
        }

        Principal realmRewrittenPrincipal = realmInfo.getPrincipalRewriter().apply(postRealmPrincipal);
        if (realmRewrittenPrincipal == null) {
            throw log.invalidName();
        }

        log.tracef("Principal mapping: [%s], pre-realm rewritten: [%s], realm name: [%s], post realm rewritten: [%s], realm rewritten: [%s]",
                principal, preRealmPrincipal, realmName, postRealmPrincipal, realmRewrittenPrincipal);

        if (realmType.isInstance(securityRealm)) {
            return fn.apply(realmType.cast(securityRealm), realmRewrittenPrincipal);
        } else {
            return nonExistent.get();
        }
    }

    SecurityRealm getRealm(final String realmName) {
        return getRealmInfo(realmName).getSecurityRealm();
    }

    RealmInfo getRealmInfo(final String realmName) {
        RealmInfo realmInfo = this.realmMap.get(realmName);

        if (realmInfo == null) {
            realmInfo = this.realmMap.get(this.defaultRealmName);
        }
        if (realmInfo == null) {
            log.tracef("Unable to obtain RealmInfo [%s] and no default set - using empty", realmName);
            realmInfo = EMPTY_REALM_INFO;
        }
        return realmInfo;
    }

    Collection getRealmInfos() {
        return realmMap.values();
    }

    /**
     * Determine whether a credential of the given type and algorithm is definitely obtainable, possibly obtainable (for
     * some identities), or definitely not obtainable.
     *
     * Credential is {@link SupportLevel#SUPPORTED}, if it is supported by all realms of the domain.
     * Credential is {@link SupportLevel#POSSIBLY_SUPPORTED} if it is supported or possibly supported by at least one realm of the domain.
     * Otherwise it is {@link SupportLevel#UNSUPPORTED}.
     *
     * @param credentialType the exact credential type (must not be {@code null})
     * @param algorithmName the algorithm name, or {@code null} if any algorithm is acceptable or the credential type does
     *  not support algorithm names
     * @param parameterSpec the algorithm parameters to match, or {@code null} if any parameters are acceptable or the credential type
     *  does not support algorithm parameters
     * @return the level of support for this credential
     */
    public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) {
        return getSupportLevel(r -> {
            try {
                return r.getCredentialAcquireSupport(credentialType, algorithmName, parameterSpec);
            } catch (RealmUnavailableException e) {
                log.trace("Failed to obtain credential acquire support from realm", e);
                return null;
            }
        });
    }

    /**
     * Determine whether a credential of the given type and algorithm is definitely obtainable, possibly obtainable (for
     * some identities), or definitely not obtainable.
     *
     * Credential is {@link SupportLevel#SUPPORTED}, if it is supported by all realms of the domain.
     * Credential is {@link SupportLevel#POSSIBLY_SUPPORTED} if it is supported or possibly supported by at least one realm of the domain.
     * Otherwise it is {@link SupportLevel#UNSUPPORTED}.
     *
     * @param credentialType the exact credential type (must not be {@code null})
     * @param algorithmName the algorithm name, or {@code null} if any algorithm is acceptable or the credential type does
     *  not support algorithm names
     * @return the level of support for this credential
     */
    public SupportLevel getCredentialAcquireSupport(Class credentialType, String algorithmName) {
        return getCredentialAcquireSupport(credentialType, algorithmName, null);
    }

    /**
     * Determine whether a credential of the given type and algorithm is definitely obtainable, possibly obtainable (for
     * some identities), or definitely not obtainable.
     *
     * Credential is {@link SupportLevel#SUPPORTED}, if it is supported by all realms of the domain.
     * Credential is {@link SupportLevel#POSSIBLY_SUPPORTED} if it is supported or possibly supported by at least one realm of the domain.
     * Otherwise it is {@link SupportLevel#UNSUPPORTED}.
     *
     * @param credentialType the exact credential type (must not be {@code null})
     * @return the level of support for this credential
     */
    public SupportLevel getCredentialAcquireSupport(Class credentialType) {
        return getCredentialAcquireSupport(credentialType, null);
    }

    /**
     * Determine whether a given type of evidence is definitely verifiable, possibly verifiable (for some identities),
     * or definitely not verifiable.
     *
     * Evidence is {@link SupportLevel#SUPPORTED}, if it is supported by all realms of the domain.
     * Evidence is {@link SupportLevel#POSSIBLY_SUPPORTED} if it is supported or possibly supported by at least one realm of the domain.
     * Otherwise it is {@link SupportLevel#UNSUPPORTED}.
     *
     * @param evidenceType the type of evidence to be verified (must not be {@code null})
     * @param algorithmName the algorithm name, or {@code null} if any algorithm is acceptable or the evidence type does
     *  not support algorithm names
     * @return the level of support for this evidence type
     */
    public SupportLevel getEvidenceVerifySupport(Class evidenceType, String algorithmName) {
        return getSupportLevel(r -> {
            try {
                return r.getEvidenceVerifySupport(evidenceType, algorithmName);
            } catch (RealmUnavailableException e) {
                log.trace("Failed to obtain evidence verify support from realm", e);
                return null;
            }
        });
    }

    /**
     * Determine whether a given type of evidence is definitely verifiable, possibly verifiable (for some identities),
     * or definitely not verifiable.
     *
     * Evidence is {@link SupportLevel#SUPPORTED}, if it is supported by all realms of the domain.
     * Evidence is {@link SupportLevel#POSSIBLY_SUPPORTED} if it is supported or possibly supported by at least one realm of the domain.
     * Otherwise it is {@link SupportLevel#UNSUPPORTED}.
     *
     * @param evidenceType the type of evidence to be verified (must not be {@code null})
     * @return the level of support for this evidence type
     */
    public SupportLevel getEvidenceVerifySupport(Class evidenceType) {
        return getEvidenceVerifySupport(evidenceType, null);
    }

    private SupportLevel getSupportLevel(final Function getSupportLevel) {
        SupportLevel min, max;
        min = max = null;
        Iterator iterator = realmMap.values().iterator();

        while (iterator.hasNext()) {
            RealmInfo realmInfo = iterator.next();
            SecurityRealm realm = realmInfo.getSecurityRealm();
            final SupportLevel support = getSupportLevel.apply(realm);

            if (support != null) {
                if (min == null || max == null) {
                    min = max = support;
                } else {
                    if (support.compareTo(min) < 0) { min = support; }
                    if (support.compareTo(max) > 0) { max = support; }
                }
            }
        }

        if (min == null || max == null) {
            return SupportLevel.UNSUPPORTED;
        } else {
            return minMax(min, max);
        }
    }

    private SupportLevel minMax(SupportLevel min, SupportLevel max) {
        if (min == max) return min;
        if (max == SupportLevel.UNSUPPORTED) {
            return SupportLevel.UNSUPPORTED;
        } else if (min == SupportLevel.SUPPORTED) {
            return SupportLevel.SUPPORTED;
        } else {
            return SupportLevel.POSSIBLY_SUPPORTED;
        }
    }

    /**
     * Get the current security identity for this domain.
     *
     * Code can be executed with given identity using {@code SecurityIdentity.runAs*} methods.
     *
     * @return the current security identity for this domain (not {@code null})
     */
    public SecurityIdentity getCurrentSecurityIdentity() {
        final SecurityIdentity identity = currentSecurityIdentity.get().get();
        return identity == null ? anonymousIdentity : identity;
    }

    /**
     * Get the anonymous security identity for this realm.
     *
     * @return the anonymous security identity for this realm (not {@code null})
     */
    public SecurityIdentity getAnonymousSecurityIdentity() {
        return anonymousIdentity;
    }

    /**
     * Create an empty ad-hoc identity.  The identity will have no authorization information and no credentials associated
     * with it.
     *
     * @param name the identity name (must not be {@code null})
     * @return the ad-hoc identity
     */
    public SecurityIdentity createAdHocIdentity(String name) {
        checkNotNullParam("name", name);
        return createAdHocIdentity(new NamePrincipal(name));
    }

    /**
     * Create an empty ad-hoc identity.  The identity will have no authorization information and no credentials associated
     * with it.
     *
     * Calling with enabled security manager requires {@code createAdHocIdentity} {@link ElytronPermission}.
     *
     * @param principal the identity principal (must not be {@code null})
     * @return the ad-hoc identity
     */
    public SecurityIdentity createAdHocIdentity(Principal principal) {
        checkNotNullParam("principal", principal);
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(CREATE_AD_HOC_IDENTITY);
        }
        return this.transform(new SecurityIdentity(this, principal, EMPTY_REALM_INFO, AuthorizationIdentity.EMPTY, emptyMap(), IdentityCredentials.NONE, IdentityCredentials.NONE));
    }

    Supplier getAndSetCurrentSecurityIdentity(Supplier newIdentity) {
        try {
            final Supplier oldIdentity = currentSecurityIdentity.get();
            return oldIdentity == null ? anonymousIdentity : oldIdentity;
        } finally {
            if (newIdentity == anonymousIdentity) {
                currentSecurityIdentity.remove();
            } else {
                currentSecurityIdentity.set(newIdentity);
            }
        }
    }

    void setCurrentSecurityIdentity(Supplier newIdentity) {
        if (newIdentity == anonymousIdentity) {
            currentSecurityIdentity.remove();
        } else {
            currentSecurityIdentity.set(newIdentity);
        }
    }

    Roles mapRoles(SecurityIdentity securityIdentity) {
        Assert.checkNotNullParam("securityIdentity", securityIdentity);

        AuthorizationIdentity identity = securityIdentity.getAuthorizationIdentity();
        RealmInfo realmInfo = securityIdentity.getRealmInfo();

        // zeroth role mapping, just grab roles from the identity
        Roles decodedRoles = realmInfo.getRoleDecoder().decodeRoles(identity);

        // determine roles based on any runtime attributes associated with the identity
        Roles domainDecodedRoles = securityIdentity.getSecurityDomain().getRoleDecoder().decodeRoles(identity);
        Roles combinedRoles = decodedRoles.or(domainDecodedRoles);

        // apply the first level mapping, which is based on the role mapper associated with a realm.
        Roles realmMappedRoles = realmInfo.getRoleMapper().mapRoles(combinedRoles);

        // apply the second level mapping, which is based on the role mapper associated with this security domain.
        Roles domainMappedRoles = roleMapper.mapRoles(realmMappedRoles);

        if (log.isTraceEnabled()) {
            log.tracef("Role mapping: principal [%s] -> decoded roles [%s] -> domain decoded roles [%s] -> realm mapped roles [%s] -> domain mapped roles [%s]",
                    securityIdentity.getPrincipal(), String.join(", ", decodedRoles), String.join(", ", domainDecodedRoles), String.join(", ", realmMappedRoles), String.join(", ", domainMappedRoles));
        }

        return domainMappedRoles;
    }

    PermissionVerifier mapPermissions(final SecurityIdentity securityIdentity) {
        Assert.checkNotNullParam("securityIdentity", securityIdentity);
        final Roles roles = securityIdentity.getRoles();
        PermissionVerifier verifier = permissionMapper.mapPermissions(securityIdentity, roles);

        if (log.isTraceEnabled()) {
            return (permission) -> {
                boolean decision = verifier.implies(permission);
                log.tracef("Permission mapping: identity [%s] with roles [%s] implies %s = %b",
                        securityIdentity.getPrincipal(), String.join(", ", roles), permission, decision);
                return decision;
            };
        } else {
            return verifier;
        }
    }

    Function getPreRealmRewriter() {
        return preRealmPrincipalRewriter;
    }

    String mapRealmName(final Principal principal, final Evidence evidence) {
        String realm = realmMapper.getRealmMapping(principal, evidence);
        return realm != null ? realm : defaultRealmName;
    }

    String getDefaultRealmName() {
        return defaultRealmName;
    }

    RealmMapper getRealmMapper() {
        return realmMapper;
    }

    Function getPostRealmRewriter() {
        return postRealmPrincipalRewriter;
    }

    RoleMapper getRoleMapper() {
        return roleMapper;
    }

    Map getCategoryRoleMappers() {
        return categoryRoleMappers;
    }

    SecurityIdentity transform(final SecurityIdentity securityIdentity) {
        Assert.checkNotNullParam("securityIdentity", securityIdentity);
        return Assert.assertNotNull(securityIdentityTransformer.apply(securityIdentity));
    }

    boolean trustsDomain(final SecurityDomain domain) {
        Assert.checkNotNullParam("domain", domain);
        return this == domain || trustedSecurityDomain.test(domain);
    }

    /**
     * Handle a {@link SecurityEvent}.
     *
     * Calling with enabled security manager requires {@code handleSecurityEvent} {@link ElytronPermission}.
     *
     * @param securityEvent {@link SecurityEvent} to be handled
     * @see Builder#setSecurityEventListener(Consumer)
     */
    public void handleSecurityEvent(final SecurityEvent securityEvent) {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(HANDLE_SECURITY_EVENT);
        }
        if (!securityEvent.getSecurityIdentity().getSecurityDomain().equals(this)) {
            log.securityEventIdentityWrongDomain();
        }
        this.securityEventListener.accept(securityEvent);
    }

    static void safeHandleSecurityEvent(final SecurityDomain domain, final SecurityEvent event) {
        checkNotNullParam("domain", domain);
        checkNotNullParam("event", event);
        try {
            domain.handleSecurityEvent(event);
        } catch (Exception e) {
            log.eventHandlerFailed(e);
        }
    }

    Function getEvidenceDecoder() {
        return evidenceDecoder;
    }

    RoleDecoder getRoleDecoder() {
        return roleDecoder;
    }

    /**
     * A builder for creating new security domains.
     */
    public static final class Builder {
        private boolean built = false;

        private final HashMap realms = new HashMap<>();
        private Function preRealmRewriter = Function.identity();
        private Function principalDecoder = Function.identity();
        private Function postRealmRewriter = Function.identity();
        private String defaultRealmName;
        private RealmMapper realmMapper = RealmMapper.DEFAULT_REALM_MAPPER;
        private RoleMapper roleMapper = RoleMapper.IDENTITY_ROLE_MAPPER;
        private PermissionMapper permissionMapper = PermissionMapper.EMPTY_PERMISSION_MAPPER;
        private Map categoryRoleMappers = emptyMap();
        private UnaryOperator securityIdentityTransformer = UnaryOperator.identity();
        private Predicate trustedSecurityDomain = domain -> false;
        private Consumer securityEventListener = e -> {};
        private Function evidenceDecoder = evidence -> evidence.getDefaultPrincipal();
        private RoleDecoder roleDecoder = RoleDecoder.EMPTY;

        Builder() {
        }

        /**
         * Sets a pre-realm name rewriter, which rewrites the authentication name before a realm is selected.
         *
         * @param rewriter the name rewriter (must not be {@code null})
         * @return this builder
         */
        public Builder setPreRealmRewriter(NameRewriter rewriter) {
            return setPreRealmRewriter(rewriter.asPrincipalRewriter());
        }

        /**
         * Sets a pre-realm name rewriter, which rewrites the authentication name before a realm is selected.
         *
         * @param rewriter the name rewriter (must not be {@code null})
         * @return this builder
         */
        public Builder setPreRealmRewriter(final Function rewriter) {
            Assert.checkNotNullParam("rewriter", rewriter);
            assertNotBuilt();
            this.preRealmRewriter = rewriter;
            return this;
        }

        /**
         * Sets a post-realm name rewriter, which rewrites the authentication name after a realm is selected.
         *
         * @param rewriter the name rewriter (must not be {@code null})
         * @return this builder
         */
        public Builder setPostRealmRewriter(NameRewriter rewriter) {
            return setPostRealmRewriter(rewriter.asPrincipalRewriter());
        }

        /**
         * Sets a post-realm name rewriter, which rewrites the authentication name after a realm is selected.
         *
         * @param rewriter the name rewriter (must not be {@code null})
         * @return this builder
         */
        public Builder setPostRealmRewriter(Function rewriter) {
            Assert.checkNotNullParam("rewriter", rewriter);
            assertNotBuilt();
            this.postRealmRewriter = rewriter;

            return this;
        }

        /**
         * Set the realm mapper for this security domain, which selects a realm based on the authentication name.
         *
         * @param realmMapper the realm mapper (must not be {@code null})
         * @return this builder
         */
        public Builder setRealmMapper(RealmMapper realmMapper) {
            Assert.checkNotNullParam("realmMapper", realmMapper);
            assertNotBuilt();
            this.realmMapper = realmMapper;

            return this;
        }

        /**
         * Set the role mapper for this security domain, which will be used to perform the last mapping before
         * returning the roles associated with an identity obtained from this security domain.
         *
         * @param roleMapper the role mapper (must not be {@code null})
         * @return this builder
         */
        public Builder setRoleMapper(RoleMapper roleMapper) {
            Assert.checkNotNullParam("roleMapper", roleMapper);
            assertNotBuilt();
            this.roleMapper = roleMapper;
            return this;
        }

        /**
         * Set the permission mapper for this security domain, which will be used to obtain and map permissions based on the
         * identities from this security domain.
         *
         * @param permissionMapper the permission mapper (must not be {@code null})
         * @return this builder
         */
        public Builder setPermissionMapper(PermissionMapper permissionMapper) {
            Assert.checkNotNullParam("permissionMapper", permissionMapper);
            assertNotBuilt();
            this.permissionMapper = permissionMapper;
            return this;
        }

        /**
         * Set the principal decoder for this security domain, which will be used to convert {@link Principal} objects
         * into names for handling in the realm.
         *
         * @param principalDecoder the principal decoder (must not be {@code null})
         * @return this builder
         */
        public Builder setPrincipalDecoder(PrincipalDecoder principalDecoder) {
            Assert.checkNotNullParam("principalDecoder", principalDecoder);
            assertNotBuilt();
            this.principalDecoder = principalDecoder.asPrincipalRewriter();
            return this;
        }

        /**
         * Add a realm to this security domain.
         *
         * @param name the realm's name in this configuration
         * @param realm the realm
         * @return the new realm builder
         */
        public RealmBuilder addRealm(String name, SecurityRealm realm) {
            Assert.checkNotNullParam("name", name);
            Assert.checkNotNullParam("realm", realm);
            assertNotBuilt();
            return new RealmBuilder(this, name, realm);
        }

        Builder addRealm(RealmBuilder realmBuilder) {
            realms.put(realmBuilder.getName(), realmBuilder);

            return this;
        }

        /**
         * Get the default realm name.
         *
         * @return the default realm name
         */
        public String getDefaultRealmName() {
            return defaultRealmName;
        }

        /**
         * Set the default realm name.
         *
         * @param defaultRealmName the default realm name (must not be {@code null})
         */
        public Builder setDefaultRealmName(final String defaultRealmName) {
            Assert.checkNotNullParam("defaultRealmName", defaultRealmName);
            assertNotBuilt();
            this.defaultRealmName = defaultRealmName;

            return this;
        }

        /**
         * Get the category role mapper map.
         *
         * @return the category role mapper map
         */
        public Map getCategoryRoleMappers() {
            return categoryRoleMappers;
        }

        /**
         * Set the category role mapper map.
         *
         * @param categoryRoleMappers the category role mapper map (must not be {@code null})
         */
        public void setCategoryRoleMappers(final Map categoryRoleMappers) {
            Assert.checkNotNullParam("categoryRoleMappers", categoryRoleMappers);
            this.categoryRoleMappers = categoryRoleMappers;
        }

        /**
         * Set the security identity transformer to use.  The transformer must not return {@code null}, or authentication
         * will fail.
         *
         * @param securityIdentityTransformer the security identity transformer to use (must not be {@code null})
         * @return this builder
         */
        public Builder setSecurityIdentityTransformer(UnaryOperator securityIdentityTransformer) {
            Assert.checkNotNullParam("securityIdentityTransformer", securityIdentityTransformer);
            this.securityIdentityTransformer = securityIdentityTransformer;
            return this;
        }

        /**
         * Set the predicate that should be used to determine if a given domain is trusted by this domain.
         *
         * @param trustedSecurityDomain the predicate that should be used to determine if a given domain is
         *                              trusted by this domain (must not be {@code null})
         */
        public Builder setTrustedSecurityDomainPredicate(final Predicate trustedSecurityDomain) {
            Assert.checkNotNullParam("trustedSecurityDomain", trustedSecurityDomain);
            this.trustedSecurityDomain = trustedSecurityDomain;
            return this;
        }

        /**
         * Set the security event listener that will consume all {@link SecurityEvent} instances emitted but the domain.
         *
         * @param securityEventListener the security event listener that will consume all {@link SecurityEvent} instances emitted but the domain.
         * @return this builder
         */
        public Builder setSecurityEventListener(final Consumer securityEventListener) {
            this.securityEventListener = Assert.checkNotNullParam("securityEventListener", securityEventListener);
            return this;
        }

        /**
         * Set the evidence decoder for this security domain which will be used to extract the principal from the given
         * {@link Evidence}.
         *
         * @param evidenceDecoder the evidence decoder (must not be {@code null})
         * @return this builder
         * @since 1.10.0
         */
        public Builder setEvidenceDecoder(EvidenceDecoder evidenceDecoder) {
            Assert.checkNotNullParam("evidenceDecoder", evidenceDecoder);
            assertNotBuilt();
            this.evidenceDecoder = evidenceDecoder;
            return this;
        }

        /**
         * Set the role decoder for this security domain.
         *
         * @param roleDecoder the role decoder (must not be {@code null})
         * @return this builder
         * @since 1.11.0
         */
        public Builder setRoleDecoder(RoleDecoder roleDecoder) {
            Assert.checkNotNullParam("roleDecoder", roleDecoder);
            assertNotBuilt();
            this.roleDecoder = roleDecoder;
            return this;
        }

        /**
         * Construct this security domain.
         *
         * Construction requires {@code createSecurityDomain} {@link ElytronPermission}.
         *
         * @return the new security domain
         */
        public SecurityDomain build() {
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                sm.checkPermission(CREATE_SECURITY_DOMAIN);
            }

            final LinkedHashMap realmMap = new LinkedHashMap<>(realms.size());

            for (RealmBuilder realmBuilder : realms.values()) {
                realmMap.put(realmBuilder.getName(), new RealmInfo(realmBuilder));
            }
            if (defaultRealmName != null && !realmMap.containsKey(defaultRealmName)) {
                throw log.realmMapDoesNotContainDefault(defaultRealmName);
            }

            assertNotBuilt();
            built = true;

            if(log.isTraceEnabled()) {
                log.tracef("Building security domain with defaultRealmName %s.", defaultRealmName);
                if(realmMap.size() > 1) {
                    log.tracef("The following additional realms were added: %s.", realmMap.keySet().toString());
                }
            }

            return new SecurityDomain(this, realmMap);
        }

        void assertNotBuilt() {
            if (built) {
                throw log.builderAlreadyBuilt();
            }
        }
    }

    /**
     * A builder for a realm within a security domain.
     */
    public static class RealmBuilder {

        private final Builder parent;
        private final String name;
        private final SecurityRealm realm;
        private RoleMapper roleMapper = RoleMapper.IDENTITY_ROLE_MAPPER;
        private Function principalRewriter = Function.identity();
        private RoleDecoder roleDecoder = RoleDecoder.DEFAULT;
        private boolean built = false;

        RealmBuilder(final Builder parent, final String name, final SecurityRealm realm) {
            this.parent = parent;
            this.name = name;
            this.realm = realm;
        }

        /**
         * Get the realm name.
         *
         * @return the realm name (not {@code null})
         */
        public String getName() {
            return name;
        }

        /**
         * Get the security realm.
         *
         * @return the security realm (not {@code null})
         */
        public SecurityRealm getRealm() {
            return realm;
        }

        /**
         * Get the role mapper.
         *
         * @return the role mapper (not {@code null})
         */
        public RoleMapper getRoleMapper() {
            return roleMapper;
        }

        /**
         * Set the role mapper.
         *
         * @param roleMapper the role mapper (may not be {@code null})
         */
        public RealmBuilder setRoleMapper(final RoleMapper roleMapper) {
            assertNotBuilt();
            Assert.checkNotNullParam("roleMapper", roleMapper);
            this.roleMapper = roleMapper;

            return this;
        }

        /**
         * Get the name rewriter.
         *
         * @return the name rewriter (not {@code null})
         */
        public Function getPrincipalRewriter() {
            return principalRewriter;
        }

        /**
         * Set the name rewriter.
         *
         * @param principalRewriter the name rewriter (may not be {@code null})
         */
        public RealmBuilder setPrincipalRewriter(final Function principalRewriter) {
            Assert.checkNotNullParam("principalRewriter", principalRewriter);
            assertNotBuilt();
            this.principalRewriter = principalRewriter;

            return this;
        }

        @Deprecated
        public RealmBuilder setNameRewriter(final NameRewriter nameRewriter) {
            return setPrincipalRewriter(nameRewriter.asPrincipalRewriter());
        }

        /**
         * Get the role decoder.
         *
         * @return the role decoder (not {@code null})
         */
        public RoleDecoder getRoleDecoder() {
            return roleDecoder;
        }

        /**
         * Set the role decoder.
         *
         * @param roleDecoder the role decoder (may not be {@code null})
         */
        public RealmBuilder setRoleDecoder(final RoleDecoder roleDecoder) {
            Assert.checkNotNullParam("roleDecoder", roleDecoder);
            assertNotBuilt();
            this.roleDecoder = roleDecoder;

            return this;
        }

        /**
         * Constructs this realm info and adds it into the domain.
         *
         * @return the security domain builder
         */
        public Builder build() {
            assertNotBuilt();
            return parent.addRealm(this);
        }

        private void assertNotBuilt() {
            parent.assertNotBuilt();
            if (built) {
                throw log.builderAlreadyBuilt();
            }
        }
    }

    private static class ScheduledExecutorServiceProvider {

        private static final ScheduledThreadPoolExecutor INSTANCE = new ScheduledThreadPoolExecutor(1, runnable -> {
            // use daemon thread
            Thread thread = Executors.defaultThreadFactory().newThread(runnable);
            thread.setDaemon(true);
            return thread;
        });

        static {
            INSTANCE.setRemoveOnCancelPolicy(true);
            INSTANCE.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
        }
    }

    /**
     * Gets {@link ScheduledExecutorService} for authentication related scheduled task (like authentication timeout).
     *
     * @return the executor service
     */
    public static ScheduledExecutorService getScheduledExecutorService() {
        return ScheduledExecutorServiceProvider.INSTANCE;
    }
}