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

io.helidon.security.Security Maven / Gradle / Ivy

There is a newer version: 4.1.6
Show newest version
/*
 * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * 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 io.helidon.security;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import io.helidon.common.CollectionsHelper;
import io.helidon.common.configurable.ThreadPoolSupplier;
import io.helidon.config.Config;
import io.helidon.security.internal.SecurityAuditEvent;
import io.helidon.security.spi.AuditProvider;
import io.helidon.security.spi.AuthenticationProvider;
import io.helidon.security.spi.AuthorizationProvider;
import io.helidon.security.spi.OutboundSecurityProvider;
import io.helidon.security.spi.ProviderSelectionPolicy;
import io.helidon.security.spi.SecurityProvider;
import io.helidon.security.spi.SecurityProviderService;
import io.helidon.security.spi.SubjectMappingProvider;

import io.opentracing.Tracer;

/**
 * This class is used to "bootstrap" security and integrate it with other frameworks; runtime
 * main entry point is {@link SecurityContext}.
 * 

* It is possible to configure it manually using {@link #builder()} or use {@link #create(Config)} to initialize using * configuration support. *

* Security is constructed from various providers {@link SecurityProvider} and * a selection policy {@link ProviderSelectionPolicy} to choose the right one(s) to * secure a request. * * @see #builder() * @see #create(Config) */ // class cannot be final, so CDI can create a proxy for it public class Security { /** * Integration should add a special header to each request. The value will contain the original * URI as was issued - for HTTP this is the relative URI including query parameters. */ public static final String HEADER_ORIG_URI = "X_ORIG_URI_HEADER"; private static final Set RESERVED_PROVIDER_KEYS = CollectionsHelper.setOf( "name", "class", "is-authentication-provider", "is-authorization-provider", "is-client-security-provider", "is-audit-provider"); private static final Set CONFIG_INTERNAL_PREFIXES = CollectionsHelper.setOf( "provider-policy", "providers", "environment" ); private static final Logger LOGGER = Logger.getLogger(Security.class.getName()); private final Collection> annotations = new LinkedList<>(); private final List> auditors = new LinkedList<>(); private final Optional subjectMappingProvider; private final String instanceUuid; private final ProviderSelectionPolicy providerSelectionPolicy; private final Tracer securityTracer; private final SecurityTime serverTime; private final Supplier executorService; private final Config securityConfig; @SuppressWarnings("unchecked") private Security(Builder builder) { this.instanceUuid = UUID.randomUUID().toString(); this.serverTime = builder.serverTime; this.executorService = builder.executorService; this.annotations.addAll(SecurityUtil.getAnnotations(builder.allProviders)); this.securityTracer = SecurityUtil.getTracer(builder.tracingEnabled, builder.tracer); this.subjectMappingProvider = Optional.ofNullable(builder.subjectMappingProvider); this.securityConfig = builder.config; //providers List> atzProviders = new LinkedList<>(); List> atnProviders = new LinkedList<>(); List> outboundProviders = new LinkedList<>(); atzProviders.addAll(builder.atzProviders); atnProviders.addAll(builder.atnProviders); outboundProviders.addAll(builder.outboundProviders); builder.auditProviders.forEach(auditProvider -> auditors.add(auditProvider.auditConsumer())); audit(instanceUuid, SecurityAuditEvent.info( AuditEvent.SECURITY_TYPE_PREFIX + ".configure", "Security initialized. Providers: audit: \"%s\"; authn: \"%s\"; authz: \"%s\"; identity propagation: \"%s\";") .addParam(AuditEvent.AuditParam.plain("auditProviders", SecurityUtil.forAudit(builder.auditProviders))) .addParam(AuditEvent.AuditParam.plain("authenticationProvider", SecurityUtil.forAuditNamed(atnProviders))) .addParam(AuditEvent.AuditParam.plain("authorizationProvider", SecurityUtil.forAuditNamed(atzProviders))) .addParam(AuditEvent.AuditParam .plain("identityPropagationProvider", SecurityUtil.forAuditNamed(outboundProviders))) ); // the "default" providers NamedProvider authnProvider = builder.authnProvider; NamedProvider authzProvider = builder.authzProvider; // now I have all providers configured, I can resolve provider selection policy providerSelectionPolicy = builder.providerSelectionPolicy.apply(new ProviderSelectionPolicy.Providers() { @SuppressWarnings("unchecked") @Override public List> getProviders(Class providerType) { if (providerType.equals(AuthenticationProvider.class)) { List> result = new LinkedList<>(); result.add((NamedProvider) authnProvider); atnProviders.stream() // remove the default provider, it was added as first .filter(pr -> pr != authnProvider) .forEach(atn -> result.add((NamedProvider) atn)); return result; } else if (providerType.equals(AuthorizationProvider.class)) { List> result = new LinkedList<>(); result.add((NamedProvider) authzProvider); atzProviders.stream() // remove the default provider, it was added as first .filter(pr -> pr != authzProvider) .forEach(atn -> result.add((NamedProvider) atn)); return result; } else if (providerType.equals(OutboundSecurityProvider.class)) { List> result = new LinkedList<>(); outboundProviders.forEach(atn -> result.add((NamedProvider) atn)); return result; } else { throw new SecurityException( "Security only supports AuthenticationProvider, AuthorizationProvider and OutboundSecurityProvider in" + " provider selection policy, not " + providerType.getName()); } } }); } /** * Creates new instance based on configuration values. *

* * @param config Config instance located on security configuration ("providers" is an expected child) * @return new instance. */ public static Security create(Config config) { Objects.requireNonNull(config, "Configuration must not be null"); return builder() .config(config) .build(); } /** * Creates new instance based on configuration values. *

* * @param config Config instance located on security configuration ("providers" is an expected child) * @return new instance. */ public static Builder builder(Config config) { Objects.requireNonNull(config, "Configuration must not be null"); return builder() .config(config); } /** * Creates {@link Builder} class. * * @return builder */ public static Builder builder() { return new Builder(); } /** * Get a set of roles the subject has, based on {@link Role Role}. * This is the set of roles as assumed by authentication provider. Authorization providers may use a different set of * roles (and context used authorization provider to check {@link SecurityContext#isUserInRole(String)}). * * @param subject Subject of a user/service * @return set of roles the user/service is in */ public static Set getRoles(Subject subject) { return subject.grants(Role.class) .stream() .map(Role::getName) .collect(Collectors.toSet()); } void audit(String tracingId, AuditEvent event) { // must build within scope of the audit method, as we want to send our caller... AuditProvider.AuditSource auditSource = AuditProvider.AuditSource.create(); for (Consumer auditor : auditors) { auditor.accept(SecurityUtil.wrapEvent(tracingId, auditSource, event)); } } /** * Time that is decisive for the server. This usually returns * accessor to current time in a specified time zone. *

* {@link SecurityTime} may be configured to a fixed point in time, intended for * testing purposes. * * @return time to access current time for security decisions */ public SecurityTime serverTime() { return serverTime; } Supplier executorService() { return executorService; } ProviderSelectionPolicy providerSelectionPolicy() { return providerSelectionPolicy; } /** * Create a new security context builder to build and instance. * This is expected to be invoked for each request/response exchange * that may be authenticated, authorized etc. Context holds the security subject... * Once your processing is done and you no longer want to keep security context, call {@link SecurityContext#logout()} to * clear subject and principals. * * @param id to use when logging, auditing etc. (e.g. some kind of tracing id). If none or empty, security instance * UUID will be used (at least to map all audit records for a single instance of security component). If * defined, security will prefix this id with security instance UUID * @return new fluent API builder to create a {@link SecurityContext} */ public SecurityContext.Builder contextBuilder(String id) { String newId = ((null == id) || id.isEmpty()) ? (instanceUuid + ":?") : (instanceUuid + ":" + id); return new SecurityContext.Builder(this) .id(newId) .executorService(executorService) .tracingTracer(securityTracer) .serverTime(serverTime); } /** * Create a new security context with the defined id and all defaults. * * @param id id of this context * @return new security context */ public SecurityContext createContext(String id) { return contextBuilder(id).build(); } /** * Returns a tracer that can be used to construct new spans. * * @return {@link Tracer}, may be a no-op tracer if tracing is disabled */ public Tracer tracer() { return securityTracer; } /** * Get the complete set of annotations expected by (all) security providers configured. * This is to be used for integration with other frameworks that support annotations. * * @return Collection of annotations expected by configured providers. */ public Collection> customAnnotations() { return annotations; } /** * The configuration of security. *

* This method will NOT return security internal configuration: *

    *
  • provider-policy
  • *
  • providers
  • *
  • environment
  • *
* * @param child the name of the child node to retrieve from config * @return a child node of security configuration * @throws IllegalArgumentException in case you request child in one of the forbidden trees */ public Config configFor(String child) { String test = child.trim(); if (test.isEmpty()) { throw new IllegalArgumentException("Root of security configuration is not available"); } for (String prefix : CONFIG_INTERNAL_PREFIXES) { if (child.equals(prefix) || child.startsWith(prefix + ".")) { throw new IllegalArgumentException("Security configuration for " + prefix + " is not available"); } } return securityConfig.get(child); } Optional resolveAtnProvider(String providerName) { return resolveProvider(AuthenticationProvider.class, providerName); } @SuppressWarnings("unchecked") Optional resolveAtzProvider(String providerName) { return resolveProvider(AuthorizationProvider.class, providerName); } List resolveOutboundProvider(String providerName) { if (null != providerName) { return resolveProvider(OutboundSecurityProvider.class, providerName).map(CollectionsHelper::listOf) .orElse(CollectionsHelper.listOf()); } return providerSelectionPolicy.selectOutboundProviders(); } @SuppressWarnings("unchecked") private Optional resolveProvider(Class providerClass, String providerName) { if (null == providerName) { return providerSelectionPolicy.selectProvider(providerClass); } Optional instance = providerSelectionPolicy.selectProvider(providerClass, providerName); if (instance.isPresent()) { return instance; } throw new SecurityException("Named " + providerClass .getSimpleName() + " expected for name \"" + providerName + "\" yet none is configured for such a name"); } /** * Security environment builder, to be used to create * environment for evaluating security in integration components. * * @return builder to build {@link SecurityEnvironment} */ public SecurityEnvironment.Builder environmentBuilder() { return SecurityEnvironment.builder(serverTime); } /** * Subject mapping provider used to map subject(s) authenticated by {@link io.helidon.security.spi.AuthenticationProvider} * to a new {@link io.helidon.security.Subject}, e.g. to add roles. * * @return subject mapping provider to use or empty if none defined */ public Optional subjectMapper() { return subjectMappingProvider; } /** * Builder pattern class for helping create {@link Security} in a convenient way. */ public static final class Builder implements io.helidon.common.Builder { private final Set auditProviders = new LinkedHashSet<>(); private final List> atnProviders = new LinkedList<>(); private final List> atzProviders = new LinkedList<>(); private final List> outboundProviders = new LinkedList<>(); private final Map allProviders = new IdentityHashMap<>(); private NamedProvider authnProvider; private NamedProvider authzProvider; private SubjectMappingProvider subjectMappingProvider; private Config config = Config.empty(); private Function providerSelectionPolicy = FirstProviderSelectionPolicy::new; private Tracer tracer; private boolean tracingEnabled = true; private SecurityTime serverTime = SecurityTime.builder().build(); private Supplier executorService = ThreadPoolSupplier.create(); private Builder() { } /** * Set the provider selection policy. * The function is used to provider an immutable instance of the {@link ProviderSelectionPolicy}. *

* Default is {@link FirstProviderSelectionPolicy}. *

* Alternative built-in policy is: {@link CompositeProviderSelectionPolicy} - you can use its {@link * CompositeProviderSelectionPolicy#builder()} * to configure it and then configure this method with {@link CompositeProviderSelectionPolicy.Builder#build()}. *

* You can also use custom policy. * * @param pspFunction function to obtain an instance of the policy. This function will be only called once by security. * @return updated builder instance */ public Builder providerSelectionPolicy(Function pspFunction) { this.providerSelectionPolicy = pspFunction; return this; } /** * Server time to use when evaluating security policies that depend on time. * * @param time time instance with possible time shift, explicit timezone or overridden values * @return updated builder instance */ public Builder serverTime(SecurityTime time) { this.serverTime = time; return this; } /** * Set an open tracing tracer to use for security. * * @param tracer Tracer to use. If null is set, tracing will be disabled. * @return updated builder instance */ public Builder tracer(Tracer tracer) { this.tracer = tracer; tracingEnabled(null != tracer); return this; } /** * Whether or not tracing should be enabled. If set to false, security tracer will be a no-op tracer. * * @param tracingEnabled true to enable tracing, false to disable * @return updated builder instance */ public Builder tracingEnabled(boolean tracingEnabled) { this.tracingEnabled = tracingEnabled; return this; } /** * Disable open tracing support in this security instance. This will cause method {@link SecurityContext#tracer()} to * return a no-op tracer. * * @return updated builder instance */ public Builder disableTracing() { return tracingEnabled(false); } /** * Add a provider, works as {@link #addProvider(SecurityProvider, String)}, where the name is set to {@link * Class#getSimpleName()}. * * @param provider Provider implementing multiple security provider interfaces * @return updated builder instance */ public Builder addProvider(SecurityProvider provider) { return addProvider(provider, provider.getClass().getSimpleName()); } /** * Add a provider, works as {@link #addProvider(SecurityProvider, String)}, where the name is set to {@link * Class#getSimpleName()}. * * @param providerBuilder Builder of a provider, method build will be immediately called * @return updated builder instance */ public Builder addProvider(Supplier providerBuilder) { return addProvider(providerBuilder.get()); } /** * Adds a named provider that may implement multiple interfaces. This is a helper method to allow you to invoke * a builder method just once. * This method will work as a chained call of add<Provider> for each provider interface your instance implements. * * @param provider Provider implementing multiple security provider interfaces * @param name name of the provider, if null, this provider will not be referencable from other scopes * @return updated builder instance */ public Builder addProvider(SecurityProvider provider, String name) { Objects.requireNonNull(provider); if (provider instanceof AuthenticationProvider) { addAuthenticationProvider((AuthenticationProvider) provider, name); } if (provider instanceof AuthorizationProvider) { addAuthorizationProvider((AuthorizationProvider) provider, name); } if (provider instanceof OutboundSecurityProvider) { addOutboundSecurityProvider((OutboundSecurityProvider) provider, name); } if (provider instanceof AuditProvider) { addAuditProvider((AuditProvider) provider); } if (provider instanceof SubjectMappingProvider) { subjectMappingProvider((SubjectMappingProvider) provider); } return this; } /** * Adds a named provider that may implement multiple interfaces. This is a helper method to allow you to invoke * a builder method just once. * This method will work as a chained call of add<Provider> for each provider interface your instance implements. * * @param providerBuilder Builder of provider implementing multiple security provider interfaces * @param name name of the provider, if null, this provider will not be referencable from other scopes * @return updated builder instance */ public Builder addProvider(Supplier providerBuilder, String name) { return addProvider(providerBuilder.get(), name); } /** * Set the default authentication provider. * * @param provider Provider instance to use as the default for this runtime. * @return updated builder instance */ public Builder authenticationProvider(AuthenticationProvider provider) { // explicit default provider this.authnProvider = new NamedProvider<>(provider.getClass().getSimpleName(), provider); return addAuthenticationProvider(provider, provider.getClass().getSimpleName()); } /** * Set the default authentication provider. * * @param builder Builder of provider to use as the default for this runtime. * @return updated builder instance */ public Builder authenticationProvider(Supplier builder) { return authenticationProvider(builder.get()); } /** * Set the default authorization provider. * * @param provider provider instance to use as the default for this runtime. * @return updated builder instance */ public Builder authorizationProvider(AuthorizationProvider provider) { // explicit default provider this.authzProvider = new NamedProvider<>(provider.getClass().getSimpleName(), provider); return addAuthorizationProvider(provider, provider.getClass().getSimpleName()); } /** * Set the default authorization provider. * * @param builder Builder of provider to use as the default for this runtime. * @return updated builder instance */ public Builder authorizationProvider(Supplier builder) { return authorizationProvider(builder.get()); } /** * Add an authentication provider. If default isn't set yet, sets it as default. * Works as {@link #addAuthenticationProvider(AuthenticationProvider, String)} where the name * is simple class name. * * @param provider provider instance to add * @return updated builder instance */ public Builder addAuthenticationProvider(AuthenticationProvider provider) { return addAuthenticationProvider(provider, provider.getClass().getSimpleName()); } /** * Add an authentication provider. If default isn't set yet, sets it as default. * Works as {@link #addAuthenticationProvider(AuthenticationProvider, String)} where the name * is simple class name. * * @param builder builder of provider to add * @return updated builder instance */ public Builder addAuthenticationProvider(Supplier builder) { return addAuthenticationProvider(builder.get()); } /** * Add a named authentication provider. Provider can be referenced by name e.g. from configuration. * * @param provider provider instance * @param name name of provider, may be null or empty, but as such will not be rerefencable by name * @return updated builder instance */ public Builder addAuthenticationProvider(AuthenticationProvider provider, String name) { Objects.requireNonNull(provider); NamedProvider namedProvider = new NamedProvider<>(name, provider); if (null == authnProvider) { this.authnProvider = namedProvider; } this.atnProviders.add(namedProvider); this.allProviders.put(provider, true); return this; } /** * Add a named authentication provider. Provider can be referenced by name e.g. from configuration. * * @param builder builder of provider instance * @param name name of provider, may be null or empty, but as such will not be rerefencable by name * @return updated builder instance */ public Builder addAuthenticationProvider(Supplier builder, String name) { return addAuthenticationProvider(builder.get(), name); } /** * Add authorization provider. If there is no default yet, it will become the default. * * @param provider provider instance * @return updated builder instance */ public Builder addAuthorizationProvider(AuthorizationProvider provider) { return addAuthorizationProvider(provider, provider.getClass().getSimpleName()); } /** * Add authorization provider. If there is no default yet, it will become the default. * * @param builder builder of provider instance * @return updated builder instance */ public Builder addAuthorizationProvider(Supplier builder) { return addAuthorizationProvider(builder.get()); } /** * Add a named authorization provider. Named authorization provider can be referenced, such as from * configuration. * * @param provider provider instance * @param name name of provider, may be null or empty, but as such will not be referencable * @return updated builder instance */ public Builder addAuthorizationProvider(AuthorizationProvider provider, String name) { Objects.requireNonNull(provider); NamedProvider namedProvider = new NamedProvider<>(name, provider); if (null == authzProvider) { this.authzProvider = namedProvider; } this.atzProviders.add(namedProvider); this.allProviders.put(provider, true); return this; } /** * Add a named authorization provider. Named authorization provider can be referenced, such as from * configuration. * * @param builder builder of provider instance * @param name name of provider, may be null or empty, but as such will not be referencable * @return updated builder instance */ public Builder addAuthorizationProvider(Supplier builder, String name) { return addAuthorizationProvider(builder.get(), name); } /** * All configured identity propagation providers are used. * The first provider to return true to * {@link OutboundSecurityProvider#isOutboundSupported(ProviderRequest, SecurityEnvironment, EndpointConfig)} * will be called to process current request. Others will be ignored. * * @param provider Provider instance * @return updated builder instance */ public Builder addOutboundSecurityProvider(OutboundSecurityProvider provider) { return addOutboundSecurityProvider(provider, provider.getClass().getSimpleName()); } /** * All configured identity propagation providers are used. * The first provider to return true to * {@link OutboundSecurityProvider#isOutboundSupported(ProviderRequest, SecurityEnvironment, EndpointConfig)} * will be called to process current request. Others will be ignored. * * @param builder Builder of provider instance * @return updated builder instance */ public Builder addOutboundSecurityProvider(Supplier builder) { return addOutboundSecurityProvider(builder.get()); } /** * Add a named outbound security provider. Explicit names can be used when using * secured client - see integration with Jersey. * * @param build Builder of provider to use * @param name name of the provider for reference from configuration * @return updated builder instance. */ public Builder addOutboundSecurityProvider(Supplier build, String name) { return addOutboundSecurityProvider(build.get(), name); } /** * Add a named outbound security provider. * * @param provider Provider to use * @param name name of the provider for reference from configuration * @return updated builder instance. */ public Builder addOutboundSecurityProvider(OutboundSecurityProvider provider, String name) { Objects.requireNonNull(provider); Objects.requireNonNull(name); this.outboundProviders.add(new NamedProvider<>(name, provider)); this.allProviders.put(provider, true); return this; } /** * Add an audit provider to this security runtime. * All configured audit providers are used. * * @param provider provider instance * @return updated builder instance */ public Builder addAuditProvider(AuditProvider provider) { this.auditProviders.add(provider); this.allProviders.put(provider, true); return this; } /** * Configure a subject mapping provider that would be used once authentication is processed. * Allows you to add {@link Grant Grants} to {@link Subject} or modify it in other ways. * * @param provider provider to use for subject mapping * @return updated builder instance */ public Builder subjectMappingProvider(SubjectMappingProvider provider) { this.subjectMappingProvider = provider; this.allProviders.put(provider, true); return this; } /** * Add an audit provider to this security runtime. * All configured audit providers are used. * * @param builder Builder of provider instance * @return updated builder instance */ public Builder addAuditProvider(Supplier builder) { return addAuditProvider(builder.get()); } /** * Add config instance to this builder. This may be later use by components initialized as a side-effect * of creating an instance of security (such as security providers). * * @param config Config instance * @return this instance */ public Builder config(Config config) { this.config = config; fromConfig(config); return this; } /** * Builds configured Security instance. * * @return built instance. */ @Override public Security build() { if (allProviders.isEmpty()) { LOGGER.warning("Security component is NOT configured with any security providers."); } if (auditProviders.isEmpty()) { DefaultAuditProvider provider = config.as(DefaultAuditProvider::create).get(); addAuditProvider(provider); } if (atnProviders.isEmpty()) { addAuthenticationProvider(context -> CompletableFuture .completedFuture(AuthenticationResponse.success(SecurityContext.ANONYMOUS)), "default"); } if (atzProviders.isEmpty()) { addAuthorizationProvider(context -> CompletableFuture .completedFuture(AuthorizationResponse.permit()), "default"); } return new Security(this); } private void fromConfig(Config config) { config.get("environment.server-time").as(SecurityTime::create).ifPresent(this::serverTime); executorSupplier(ThreadPoolSupplier.create(config.get("environment.executor-service"))); Map configKeyToService = new HashMap<>(); Map classNameToService = new HashMap<>(); //add all providers from service loaders String knownKeys = loadProviderServices(configKeyToService, classNameToService); config.get("tracing.enabled").as(Boolean.class).ifPresent(this::tracingEnabled); //iterate through all providers and find them config.get("providers").asList(Config.class).get().forEach(pConf -> { AtomicReference service = new AtomicReference<>(); AtomicReference providerSpecific = new AtomicReference<>(); // if we have name and class, use them String className = pConf.get("class").asString().orElse(null); if (null == className) { findProviderService(configKeyToService, knownKeys, pConf, service, providerSpecific); } else { // explicit class name - the most detailed configuration possible SecurityProviderService providerService = classNameToService.get(className); if (null == providerService) { findProviderSpecificConfig(pConf, providerSpecific); } else { service.set(providerService); providerSpecific.set(pConf.get(providerService.providerConfigKey())); } } Config providerSpecificConfig = providerSpecific.get(); SecurityProviderService providerService = service.get(); if ((null == className) && (null == providerService)) { throw new SecurityException( "Each configured provider MUST have a \"class\" configuration property defined or a custom " + "configuration section mapped to that provider, supported keys: " + knownKeys); } String name = resolveProviderName(pConf, className, providerSpecificConfig, providerService); boolean isAuthn = pConf.get("is-authentication-provider").asBoolean().orElse(true); boolean isAuthz = pConf.get("is-authorization-provider").asBoolean().orElse(true); boolean isClientSec = pConf.get("is-client-security-provider").asBoolean().orElse(true); boolean isAudit = pConf.get("is-audit-provider").asBoolean().orElse(true); boolean isSubjectMapper = pConf.get("is-subject-mapper").asBoolean().orElse(true); SecurityProvider provider; if (null == providerService) { provider = SecurityUtil.instantiate(className, SecurityProvider.class, providerSpecificConfig); } else { provider = providerService.providerInstance(providerSpecificConfig); } if (isAuthn && (provider instanceof AuthenticationProvider)) { addAuthenticationProvider((AuthenticationProvider) provider, name); } if (isAuthz && (provider instanceof AuthorizationProvider)) { addAuthorizationProvider((AuthorizationProvider) provider, name); } if (isClientSec && (provider instanceof OutboundSecurityProvider)) { addOutboundSecurityProvider((OutboundSecurityProvider) provider, name); } if (isAudit && (provider instanceof AuditProvider)) { addAuditProvider((AuditProvider) provider); } if (isSubjectMapper && (provider instanceof SubjectMappingProvider)) { subjectMappingProvider((SubjectMappingProvider) provider); } }); if (allProviders.isEmpty()) { throw new SecurityException( "Security is not configured. At least one security provider MUST be present."); } String defaultAtnProvider = config.get("default-authentication-provider").asString().orElse(null); if (null != defaultAtnProvider) { authenticationProvider(atnProviders.stream() .filter(nsp -> nsp.getName().equals(defaultAtnProvider)) .findFirst() .map(NamedProvider::getProvider) .orElseThrow(() -> new SecurityException("Authentication provider named \"" + defaultAtnProvider + "\" is set" + " as " + "default, yet no provider " + "configuration exists"))); } String defaultAtzProvider = config.get("default-authorization-provider").asString().orElse(null); if (null != defaultAtzProvider) { authorizationProvider(atzProviders.stream() .filter(nsp -> nsp.getName().equals(defaultAtzProvider)) .findFirst() .map(NamedProvider::getProvider) .orElseThrow(() -> new SecurityException("Authorization provider named \"" + defaultAtzProvider + "\" is set" + " as " + "default, yet no provider " + "configuration exists"))); } // now policy config = config.get("provider-policy"); ProviderSelectionPolicyType pType = config.get("type") .asString() .map(ProviderSelectionPolicyType::valueOf) .orElse(ProviderSelectionPolicyType.FIRST); switch (pType) { case FIRST: providerSelectionPolicy = FirstProviderSelectionPolicy::new; break; case COMPOSITE: providerSelectionPolicy = CompositeProviderSelectionPolicy.create(config); break; case CLASS: providerSelectionPolicy = findProviderSelectionPolicy(config); break; default: throw new IllegalStateException("Invalid enum option: " + pType + ", probably version mis-match"); } } private void executorSupplier(Supplier supplier) { this.executorService = supplier; } private String resolveProviderName(Config pConf, String className, Config providerSpecificConfig, SecurityProviderService providerService) { return pConf.get("name").asString().orElseGet(() -> { if (null == providerSpecificConfig) { if (null == className) { return providerService.providerClass().getSimpleName(); } else { int index = className.indexOf('.'); if (index > -1) { return className.substring(index + 1); } return className; } } else { return providerSpecificConfig.name(); } }); } private void findProviderSpecificConfig(Config pConf, AtomicReference providerSpecific) { // no service for this class, must choose the configuration by selection pConf.asNodeList().get().stream().filter(this::notReservedProviderKey).forEach(providerSpecificConf -> { if (!providerSpecific.compareAndSet(null, providerSpecificConf)) { throw new SecurityException("More than one provider configurations found, each provider can only" + " have one provide specific config. Conflict: " + providerSpecific.get().key() + " and " + providerSpecificConf.key()); } }); } private void findProviderService(Map configKeyToService, String knownKeys, Config pConf, AtomicReference service, AtomicReference providerSpecific) { // everything else is based on provider specific configuration pConf.asNodeList().get().stream().filter(this::notReservedProviderKey).forEach(providerSpecificConf -> { if (!providerSpecific.compareAndSet(null, providerSpecificConf)) { throw new SecurityException("More than one provider configurations found, each provider can only" + " have one provider specific config. Conflict: " + providerSpecific.get().key() + " and " + providerSpecificConf.key()); } String keyName = providerSpecificConf.name(); if (configKeyToService.containsKey(keyName)) { service.set(configKeyToService.get(keyName)); } else { throw new SecurityException("Configuration key " + providerSpecificConf.key() + " is not a valid provider configuration. Supported keys: " + knownKeys); } }); } private String loadProviderServices(Map configKeyToService, Map classNameToService) { Set configKeys = new HashSet<>(); ServiceLoader loader = ServiceLoader.load(SecurityProviderService.class); loader.forEach(service -> { String configKey = service.providerConfigKey(); if (null != configKey) { configKeyToService.put(configKey, service); configKeys.add(configKey); } Class theClass = service.providerClass(); classNameToService.put(theClass.getName(), service); }); return String.join(", ", configKeys); } private boolean notReservedProviderKey(Config config) { return !RESERVED_PROVIDER_KEYS.contains(config.name()); } private Function findProviderSelectionPolicy(Config config) { Class clazz = config.get("class-name").as(Class.class).orElseThrow(() -> new java.lang.SecurityException( "You have configured a CLASS provider selection without configuring class-name")); if (!ProviderSelectionPolicy.class.isAssignableFrom(clazz)) { throw new SecurityException("Class " + clazz.getName() + " does not implement ProviderSelectionPolicy"); } @SuppressWarnings("unchecked") Class pspClass = clazz; //now let's find the constructor try { Constructor constructor = pspClass .getConstructor(ProviderSelectionPolicy.Providers.class, Config.class); // java9 //if (constructor.canAccess(null)) { // java8 if (ReflectionUtil.canAccess(getClass(), constructor)) { return providers -> { try { return constructor.newInstance(providers, config); } catch (Exception e) { throw new SecurityException("Failed to instantiate ProviderSelectionPolicy", e); } }; } else { throw new SecurityException("Constructor " + constructor + " of class " + clazz .getName() + " is not accessible"); } } catch (NoSuchMethodException ignored) { // ignore } try { Constructor constructor = pspClass .getConstructor(ProviderSelectionPolicy.Providers.class); // java9 //if (constructor.canAccess(null)) { // java8 if (ReflectionUtil.canAccess(getClass(), constructor)) { return providers -> { try { return constructor.newInstance(providers); } catch (Exception e) { throw new SecurityException("Failed to instantiate ProviderSelectionPolicy", e); } }; } else { throw new SecurityException("Constructor " + constructor + " of class " + clazz .getName() + " is not accessible"); } } catch (NoSuchMethodException e) { throw new SecurityException("You have configured " + clazz .getName() + " as provider selection policy class, yet it is missing public constructor with Providers " + "or Providers and Config as parameters.", e); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy