/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 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.client;
import static org.wildfly.security._private.ElytronMessages.log;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.function.UnaryOperator;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.security.auth.callback.CallbackHandler;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslClientFactory;
import javax.security.sasl.SaslException;
import org.wildfly.common.Assert;
import org.wildfly.security.SecurityFactory;
import org.wildfly.security.auth.principal.AnonymousPrincipal;
import org.wildfly.security.permission.ElytronPermission;
/**
* A client for consuming authentication context configurations.
*
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
public final class AuthenticationContextConfigurationClient {
private static final ElytronPermission CREATE_PERMISSION = new ElytronPermission("createAuthenticationContextConfigurationClient");
/**
* A reusable privileged action to create a new configuration client.
*/
public static final PrivilegedAction<AuthenticationContextConfigurationClient> ACTION = AuthenticationContextConfigurationClient::new;
/**
* Construct a new instance.
*
* @throws SecurityException if the caller does not have permission to instantiate this class
*/
public AuthenticationContextConfigurationClient() throws SecurityException {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(CREATE_PERMISSION);
}
}
/**
* Get the authentication configuration which matches the given URI, or {@link AuthenticationConfiguration#EMPTY} if there is none.
*
* @param uri the URI to match (must not be {@code null})
* @param authenticationContext the authentication context to examine (must not be {@code null})
* @return the matching configuration
*/
public AuthenticationConfiguration getAuthenticationConfiguration(URI uri, AuthenticationContext authenticationContext) {
return getAuthenticationConfiguration(uri, authenticationContext, -1);
}
/**
* Get the authentication configuration which matches the given URI, or {@link AuthenticationConfiguration#EMPTY} if there is none, setting
* a default protocol port.
*
* @param uri the URI to match (must not be {@code null})
* @param authenticationContext the authentication context to examine (must not be {@code null})
* @param protocolDefaultPort the protocol-default port
* @return the matching configuration
*/
public AuthenticationConfiguration getAuthenticationConfiguration(URI uri, AuthenticationContext authenticationContext, int protocolDefaultPort) {
return getAuthenticationConfiguration(uri, authenticationContext, protocolDefaultPort, null, null);
}
/**
* Get the authentication configuration which matches the given URI and type, or {@link AuthenticationConfiguration#EMPTY} if there is none, setting
* a default protocol port.
*
* @param uri the URI to match (must not be {@code null})
* @param authenticationContext the authentication context to examine (must not be {@code null})
* @param protocolDefaultPort the protocol-default port
* @param abstractType the abstract type (may be {@code null})
* @param abstractTypeAuthority the abstract type authority (may be {@code null})
* @return the matching configuration
*/
public AuthenticationConfiguration getAuthenticationConfiguration(URI uri, AuthenticationContext authenticationContext, int protocolDefaultPort, String abstractType, String abstractTypeAuthority) {
return getAuthenticationConfiguration(uri, authenticationContext, protocolDefaultPort, abstractType, abstractTypeAuthority, null);
}
/**
* Get the authentication configuration which matches the given URI and type, or {@link AuthenticationConfiguration#EMPTY} if there is none, setting
* a default protocol port.
*
* @param uri the URI to match (must not be {@code null})
* @param authenticationContext the authentication context to examine (must not be {@code null})
* @param protocolDefaultPort the protocol-default port
* @param abstractType the abstract type (may be {@code null})
* @param abstractTypeAuthority the abstract type authority (may be {@code null})
* @param purpose the authentication purpose (may be {@code null})
* @return the matching configuration
*/
public AuthenticationConfiguration getAuthenticationConfiguration(URI uri, AuthenticationContext authenticationContext, int protocolDefaultPort, String abstractType, String abstractTypeAuthority, String purpose) {
Assert.checkNotNullParam("uri", uri);
Assert.checkNotNullParam("authenticationContext", authenticationContext);
final RuleNode<AuthenticationConfiguration> node = authenticationContext.authRuleMatching(uri, abstractType, abstractTypeAuthority, purpose);
AuthenticationConfiguration configuration = node != null ? node.getConfiguration() : AuthenticationConfiguration.EMPTY;
final String uriHost = uri.getHost();
if (uriHost != null && configuration.setHost == null) {
configuration = configuration.useHost(uriHost);
}
int port = uri.getPort();
if (port == -1) port = protocolDefaultPort;
if (port != -1 && configuration.setPort == -1) {
// use the URI port in this configuration
configuration = configuration.usePort(port);
}
final String userInfo = uri.getUserInfo();
if (userInfo != null && configuration.getPrincipal() == AnonymousPrincipal.getInstance()) {
configuration = configuration.useName(userInfo);
}
log.tracef("getAuthenticationConfiguration uri=%s, protocolDefaultPort=%d, abstractType=%s, abstractTypeAuthority=%s, purpose=%s, MatchRule=[%s], AuthenticationConfiguration=[%s]",
uri, protocolDefaultPort, abstractType, abstractTypeAuthority, purpose, node != null ? node.rule : null, configuration);
return configuration;
}
/**
* Get the SSL context which matches the given URI, or {@link SSLContext#getDefault()} if there is none.
*
* @param uri the URI to match (must not be {@code null})
* @param authenticationContext the authentication context to examine (must not be {@code null})
* @return the matching SSL context
*/
public SSLContext getSSLContext(URI uri, AuthenticationContext authenticationContext) throws GeneralSecurityException {
return getSSLContext(uri, authenticationContext, null, null);
}
/**
* Get the SSL context which matches the given URI and type, or {@link SSLContext#getDefault()} if there is none.
*
* @param uri the URI to match (must not be {@code null})
* @param authenticationContext the authentication context to examine (must not be {@code null})
* @param abstractType the abstract type (may be {@code null})
* @param abstractTypeAuthority the abstract type authority (may be {@code null})
* @return the matching SSL context
*/
public SSLContext getSSLContext(URI uri, AuthenticationContext authenticationContext, String abstractType, String abstractTypeAuthority) throws GeneralSecurityException {
return getSSLContext(uri, authenticationContext, abstractType, abstractTypeAuthority, null);
}
/**
* Get the SSL context which matches the given URI and type, or {@link SSLContext#getDefault()} if there is none.
*
* @param uri the URI to match (must not be {@code null})
* @param authenticationContext the authentication context to examine (must not be {@code null})
* @param abstractType the abstract type (may be {@code null})
* @param abstractTypeAuthority the abstract type authority (may be {@code null})
* @param purpose the authentication purpose (may be {@code null})
* @return the matching SSL context
*/
public SSLContext getSSLContext(URI uri, AuthenticationContext authenticationContext, String abstractType, String abstractTypeAuthority, String purpose) throws GeneralSecurityException {
return getSSLContextFactory(uri, authenticationContext, abstractType, abstractTypeAuthority, purpose).create();
}
/**
* Get the SSL context factory which matches the given URI and type, or {@link SSLContext#getDefault()} if there is none.
*
* @param uri the URI to match (must not be {@code null})
* @param authenticationContext the authentication context to examine (must not be {@code null})
* @param abstractType the abstract type (may be {@code null})
* @param abstractTypeAuthority the abstract type authority (may be {@code null})
* @param purpose the authentication purpose (may be {@code null})
* @return the matching SSL context factory (not {@code null})
*/
public SecurityFactory<SSLContext> getSSLContextFactory(URI uri, AuthenticationContext authenticationContext, String abstractType, String abstractTypeAuthority, String purpose) {
Assert.checkNotNullParam("uri", uri);
Assert.checkNotNullParam("authenticationContext", authenticationContext);
final RuleNode<SecurityFactory<SSLContext>> node = authenticationContext.sslRuleMatching(uri, abstractType, abstractTypeAuthority, purpose);
if (node == null) return SSLContext::getDefault;
return node.getConfiguration();
}
/**
* Get an authentication callback handler for the given configuration.
*
* @param configuration the configuration (must not be {@code null})
* @return the callback handler
*/
public CallbackHandler getCallbackHandler(AuthenticationConfiguration configuration) {
Assert.checkNotNullParam("configuration", configuration);
final CallbackHandler callbackHandler = configuration.getUserCallbackHandler();
return callbackHandler == null ? configuration.createCallbackHandler() : callbackHandler;
}
/**
* Get the actual host to use for the given configuration and URI.
*
* @param uri the URI (must not be {@code null})
* @param configuration the configuration (must not be {@code null})
* @return the real host to use
*/
public String getRealHost(URI uri, AuthenticationConfiguration configuration) {
Assert.checkNotNullParam("uri", uri);
Assert.checkNotNullParam("configuration", configuration);
final String configurationHost = configuration.getHost();
return configurationHost == null ? uri.getHost() : configurationHost;
}
/**
* Get the actual host to use for the given configuration.
*
* @param configuration the configuration (must not be {@code null})
* @return the real host to use
*/
public String getRealHost(AuthenticationConfiguration configuration) {
Assert.checkNotNullParam("configuration", configuration);
return configuration.getHost();
}
/**
* Get the actual port to use for the given configuration and URI.
*
* @param uri the URI (must not be {@code null})
* @param configuration the configuration (must not be {@code null})
* @return the real port to use
*/
public int getRealPort(URI uri, AuthenticationConfiguration configuration) {
Assert.checkNotNullParam("uri", uri);
Assert.checkNotNullParam("configuration", configuration);
final int configurationPort = configuration.getPort();
return configurationPort == -1 ? uri.getPort() : configurationPort;
}
/**
* Get the actual port to use for the given configuration.
*
* @param configuration the configuration (must not be {@code null})
* @return the real port to use
*/
public int getRealPort(AuthenticationConfiguration configuration) {
Assert.checkNotNullParam("configuration", configuration);
return configuration.getPort();
}
/**
* Get the actual protocol to use for the given configuration and URI.
*
* @param uri the URI (must not be {@code null})
* @param configuration the configuration (must not be {@code null})
* @return the actual protocol to use, or {@code null} if none is configured and none is present on the URI
*/
public String getRealProtocol(URI uri, AuthenticationConfiguration configuration) {
Assert.checkNotNullParam("uri", uri);
Assert.checkNotNullParam("configuration", configuration);
final String protocol = configuration.getProtocol();
return protocol == null ? uri.getScheme() : protocol;
}
/**
* Get the actual protocol to use for the given configuration.
*
* @param configuration the configuration (must not be {@code null})
* @return the actual protocol to use, or {@code null} if none is configured
*/
public String getRealProtocol(AuthenticationConfiguration configuration) {
Assert.checkNotNullParam("configuration", configuration);
return configuration.getProtocol();
}
/**
* Get the authentication principal to use for the given configuration.
*
* @param configuration the configuration (must not be {@code null})
* @return the authentication principal (not {@code null})
*/
public Principal getPrincipal(AuthenticationConfiguration configuration) {
Assert.checkNotNullParam("configuration", configuration);
return configuration.getPrincipal();
}
/**
* Get the authorization principal to use for the given configuration.
*
* @param configuration the configuration (must not be {@code null})
* @return the authorization principal, or {@code null} if none is specified
*/
public Principal getAuthorizationPrincipal(AuthenticationConfiguration configuration) {
Assert.checkNotNullParam("configuration", configuration);
return configuration.getAuthorizationPrincipal();
}
/**
* Create a SASL client using the given URI and configuration from the given SASL client factory.
*
* @param uri the target URI (must not be {@code null})
* @param configuration the authentication configuration (must not be {@code null})
* @param offeredMechanisms the available mechanisms (must not be {@code null})
* @return the SASL client, or {@code null} if no clients were available or could be configured
*/
public SaslClient createSaslClient(URI uri, AuthenticationConfiguration configuration, Collection<String> offeredMechanisms) throws SaslException {
return createSaslClient(uri, configuration, offeredMechanisms, UnaryOperator.identity());
}
/**
* Create a SASL client using the given URI and configuration from the given SASL client factory.
*
* @param uri the target URI (must not be {@code null})
* @param configuration the authentication configuration (must not be {@code null})
* @param offeredMechanisms the available mechanisms (must not be {@code null})
* @param factoryOperator A {@link UnaryOperator<SaslClientFactory>} to apply to the factory used.
* @return the SASL client, or {@code null} if no clients were available or could be configured
*/
public SaslClient createSaslClient(URI uri, AuthenticationConfiguration configuration, Collection<String> offeredMechanisms, UnaryOperator<SaslClientFactory> factoryOperator) throws SaslException {
return createSaslClient(uri, configuration, offeredMechanisms, factoryOperator, null);
}
/**
* Create a SASL client using the given URI and configuration from the given SASL client factory.
*
* @param uri the target URI (must not be {@code null})
* @param configuration the authentication configuration (must not be {@code null})
* @param offeredMechanisms the available mechanisms (must not be {@code null})
* @param factoryOperator A {@link UnaryOperator<SaslClientFactory>} to apply to the factory used.
* @param sslSession the SSL session active for this connection, or {@code null} for none
* @return the SASL client, or {@code null} if no clients were available or could be configured
*/
public SaslClient createSaslClient(URI uri, AuthenticationConfiguration configuration, Collection<String> offeredMechanisms, UnaryOperator<SaslClientFactory> factoryOperator, final SSLSession sslSession) throws SaslException {
return configuration.createSaslClient(uri, offeredMechanisms, factoryOperator, sslSession);
}
/**
* Get the address of the destination from a configuration and URI. The configuration may rewrite the destination as needed.
*
* @param uri the connection URI (must not be {@code null})
* @param configuration the authentication configuration to use (must not be {@code null})
* @param protocolDefaultPort the default port for the protocol
* @return the address of the destination
*/
public InetSocketAddress getDestinationInetSocketAddress(URI uri, AuthenticationConfiguration configuration, int protocolDefaultPort) {
Assert.checkNotNullParam("uri", uri);
Assert.checkNotNullParam("configuration", configuration);
String host = configuration.getHost();
if (host == null) host = uri.getHost();
int port = configuration.getPort();
if (port == -1) port = uri.getPort();
if (port == -1) port = protocolDefaultPort;
return new InetSocketAddress(host, port);
}
/**
* Get the address of the destination from a configuration. The configuration may rewrite the destination as needed.
*
* @param configuration the authentication configuration to use (must not be {@code null})
* @return the address of the destination
*/
public InetSocketAddress getDestinationInetSocketAddress(AuthenticationConfiguration configuration) {
Assert.checkNotNullParam("configuration", configuration);
return new InetSocketAddress(configuration.getHost(), configuration.getPort());
}
/**
* Connect a plain socket to the given URI.
*
* @param uri the connection URI
* @param configuration the authentication configuration to use
* @param protocolDefaultPort the default port for the protocol used in the URI
* @return the connected socket
* @throws IOException if socket creation or connection fails for some reason
*/
public Socket connect(URI uri, AuthenticationConfiguration configuration, int protocolDefaultPort) throws IOException {
final InetSocketAddress address = getDestinationInetSocketAddress(uri, configuration, protocolDefaultPort);
return new Socket(address.getAddress(), address.getPort());
}
}