[java-plugin-shibd] 02/02: External agent resolver support, flat file resolver WIP.
Codeberg
noreply at shibboleth.net
Tue Nov 3 20:56:06 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-plugin-shibd.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd/commit/47b3a5f25f8ebaab1c1205fb2f504db151c0c2bf
commit 47b3a5f25f8ebaab1c1205fb2f504db151c0c2bf
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue Nov 3 15:55:48 2026 -0500
External agent resolver support, flat file resolver WIP.
---
.../net/shibboleth/sp/conf/agents-system.xml | 3 +-
.../sp/impl/AbstractExternalAgentResolver.java | 966 +++++++++++++++++++++
.../shibboleth/sp/impl/DefaultAgentResolver.java | 40 +-
.../sp/impl/FlatFileExternalAgentResolver.java | 357 ++++++++
4 files changed, 1362 insertions(+), 4 deletions(-)
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
index dd240c6..7b06b46 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
@@ -16,7 +16,8 @@
<context:annotation-config />
<!-- Exposes auto-wired resolver of Agents to surrounding service instance. -->
- <bean class="net.shibboleth.sp.impl.DefaultAgentResolver" />
+ <bean class="net.shibboleth.sp.impl.DefaultAgentResolver"
+ p:externalResolvers="#{getObject('shibboleth.sp.ExternalAgentResolvers')}" />
<!-- Exposes auto-wired protocol support information from protocol plugins. -->
<bean id="ProtocolSupportServiceManager" class="net.shibboleth.sp.impl.ProtocolSupportServiceManager" />
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/AbstractExternalAgentResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/AbstractExternalAgentResolver.java
new file mode 100644
index 0000000..3a58d0a
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/AbstractExternalAgentResolver.java
@@ -0,0 +1,966 @@
+/*
+ * 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 net.shibboleth.sp.impl;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.security.config.SecurityConfiguration;
+import org.opensaml.security.credential.CredentialResolver;
+import org.slf4j.Logger;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
+import net.shibboleth.idp.attribute.filter.AttributeFilter;
+import net.shibboleth.idp.attribute.resolver.AttributeResolver;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.relyingparty.RelyingPartyConfiguration;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NonNegative;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.net.IPRange;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.Resolver;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.service.ReloadableService;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.Application;
+import net.shibboleth.sp.criteria.AgentCredentialCriterion;
+import net.shibboleth.sp.criteria.AgentIDCriterion;
+import net.shibboleth.sp.state.StateManager;
+
+/**
+ * Abstract component that builds {@link Agent} objects by loading information from
+ * an external source in response to queries and applying locally configured settings.
+ *
+ * <p>The required criteria supported are {@link AgentIDCriterion} and {@link AgentCredentialCriterion}.</p>
+ *
+ * <p>Includes optional caching.</p>
+ *
+ * <p>The resolver is configured with any of the possible settings that must be settable on the
+ * resulting object, which span the {@link Agent}, {@link Application}, and {@link RelyingPartyConfiguration}
+ * interfaces.</p>
+ */
+public abstract class AbstractExternalAgentResolver extends AbstractIdentifiableInitializableComponent
+ implements Resolver<Agent,CriteriaSet> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractExternalAgentResolver.class);
+
+ /** Protocol manager to inject into agents. */
+ @Nonnull private ProtocolSupportServiceManager protocolSupportManager;
+
+ /** Whether to enable caching. */
+ private boolean cacheEnabled;
+
+ // Agent fields.
+
+ /** Size of cache. */
+ @Nonnull @Positive private Integer maximumCachedElements;
+
+ /** Sets cache expiration based on last access time. */
+ @Nullable @Positive private Duration expireAfterAccess;
+
+ /** Sets cache expiration based on entry creation. */
+ @Nullable @Positive private Duration expireAfterWrite;
+
+ /** Agent resolution cache. */
+ @Nullable private Cache<CriteriaSet,Agent> resultsCache;
+
+ /** Allowed address ranges. */
+ @Nonnull private Set<IPRange> allowedAddressRanges;
+
+ /** Whether cached authentication is supported. */
+ private boolean supportsCachedAuthentication;
+
+ /** Authentication method. */
+ @Nullable @NotEmpty String authenticationMethod;
+
+ /** Whether to support form submission preservation and recovery. */
+ private boolean supportsPostPreservation;
+
+ /** Limit on size of form data to preserve. */
+ private long postLimit;
+
+ /** Character set encoding for POST recovery. */
+ @Nonnull private Charset encoding;
+
+ /** Authorized utility flows. */
+ @Nonnull private Set<String> authorizedOperations;
+
+ // Default Application fields.
+
+ /** Lookup strategy for authenticating authority. */
+ @Nonnull private Function<ProfileRequestContext,String> authenticatingAuthorityLookupStrategy;
+
+ /** Session initiator list lookup strategy. */
+ @Nullable private Function<ProfileRequestContext,List<String>> sessionInitiatorLookupStrategy;
+
+ /** Discovery service lookup strategy. */
+ @Nonnull private Function<ProfileRequestContext,String> discoveryServiceLookupStrategy;
+
+ /** Token consumer list lookup strategy. */
+ @Nullable private Function<ProfileRequestContext,List<String>> tokenConsumerLookupStrategy;
+
+ /** Logout initiator list lookup strategy. */
+ @Nullable private Function<ProfileRequestContext,List<String>> logoutInitiatorLookupStrategy;
+
+ /** Logout consumer list lookup strategy. */
+ @Nullable private Function<ProfileRequestContext,List<String>> logoutConsumerLookupStrategy;
+
+ /** Logout admin list lookup strategy. */
+ @Nullable private Function<ProfileRequestContext,List<String>> logoutAdminLookupStrategy;
+
+ /** State management. */
+ @NonnullAfterInit private StateManager stateManager;
+
+ /** Transcoding registry. */
+ @NonnullAfterInit private ReloadableService<AttributeTranscoderRegistry> transcodingRegistry;
+
+ /** Attribute resolver. */
+ @NonnullAfterInit private ReloadableService<AttributeResolver> attributeResolver;
+
+ /** Attribute filter. */
+ @NonnullAfterInit private ReloadableService<AttributeFilter> attributeFilter;
+
+ /** Access to credentials indirectly. */
+ @Nullable private CredentialResolver credentialResolver;
+
+ // Default RelyingPartyConfiguration fields.
+
+ /** Lookup function to supply issuer. */
+ @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Controls whether detailed information about errors should be exposed. */
+ @Nonnull private Predicate<ProfileRequestContext> detailedErrorsPredicate;
+
+ /** Lookup function to supply securityConfiguration property. */
+ @Nonnull private Function<ProfileRequestContext,SecurityConfiguration> securityConfigurationLookupStrategy;
+
+ /** Lookup function to supply <code>profileConfigurations</code> property. */
+ @Nonnull
+ private Function<ProfileRequestContext,Map<String,ProfileConfiguration>> profileConfigurationsLookupStrategy;
+
+ /**
+ * Constructor.
+ *
+ * @param protocolManager injected plugin capability objects
+ */
+ public AbstractExternalAgentResolver(
+ @ParameterName(name="protocolManager") @Nonnull final ProtocolSupportServiceManager protocolManager) {
+ protocolSupportManager = Constraint.isNotNull(protocolManager, "Protocol manager cannot be null");
+
+ maximumCachedElements = 100;
+ expireAfterAccess = Duration.ofHours(4);
+
+ // Agent fields.
+ allowedAddressRanges = CollectionSupport.emptySet();
+ supportsCachedAuthentication = true;
+ authenticationMethod = null;
+ supportsPostPreservation = false;
+ postLimit = 1024 * 1024;
+ encoding = StandardCharsets.UTF_8;
+ authorizedOperations = CollectionSupport.emptySet();
+
+ // Default Application fields.
+ authenticatingAuthorityLookupStrategy = FunctionSupport.constant(null);
+ discoveryServiceLookupStrategy = FunctionSupport.constant(null);
+
+ // Default RelyingPartyConfiguration fields.
+ issuerLookupStrategy = FunctionSupport.constant(null);
+ detailedErrorsPredicate = PredicateSupport.alwaysFalse();
+ securityConfigurationLookupStrategy = FunctionSupport.constant(null);
+ profileConfigurationsLookupStrategy = FunctionSupport.constant(null);
+
+ }
+
+ /**
+ * Sets whether to enable caching.
+ *
+ * <p>Defaults to false.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setCacheEnabled(final boolean flag) {
+ cacheEnabled = flag;
+ }
+
+ /**
+ * Sets the maximum cache size.
+ *
+ * <p>Defaults to 100.</p>
+ *
+ * @param max maximum size
+ */
+ public void setMaximumCachedElements(@Nonnull @Positive final Integer max) {
+ checkSetterPreconditions();
+
+ maximumCachedElements = Constraint.isGreaterThan(0, max, "Maximum cache size must be greater than zero");
+ }
+
+ /**
+ * Sets the cache policy to expire entries after access, with the time reset on each access.
+ *
+ * <p>Defaults to 4 hours.</p>
+ *
+ * <p>Mutually exclusive with {@link #setExpireAfterWrite(Duration)}.</p>
+ *
+ * @param exp threshold
+ */
+ public void setExpireAfterAccess(@Nullable @Positive final Duration exp) {
+ checkSetterPreconditions();
+
+ if (exp != null) {
+ Constraint.isFalse(exp.isNegative() || exp.isZero(), "Thresold must be greater than zero");
+ expireAfterAccess = exp;
+ } else {
+ expireAfterAccess = null;
+ }
+ }
+
+ /**
+ * Sets the cache policy to expire entries after creation.
+ *
+ * <p>Defaults to null.</p>
+ *
+ * <p>Mutually exclusive with {@link #setExpireAfterAccess(Duration)}.</p>
+ *
+ * @param exp threshold
+ */
+ public void setExpireAfterWrite(@Nullable @Positive final Duration exp) {
+ checkSetterPreconditions();
+
+ if (exp != null) {
+ Constraint.isFalse(exp.isNegative() || exp.isZero(), "Thresold must be greater than zero");
+ expireAfterWrite = exp;
+ } else {
+ expireAfterWrite = null;
+ }
+ }
+
+ /**
+ * Sets the network ranges from which this loader's agents may make requests.
+ *
+ * @param ranges allowed ranges
+ */
+ public void setAllowedAddressRanges(@Nullable final Set<IPRange> ranges) {
+ checkSetterPreconditions();
+
+ allowedAddressRanges = ranges != null ? CollectionSupport.copyToSet(ranges) : CollectionSupport.emptySet();
+ }
+
+ /**
+ * Sets the authentication method to require for this loader's agents.
+ *
+ * <p>Defaults to {@link Agent#AUTH_METHOD_BASIC}.</p>
+ *
+ * @param method method identifier
+ */
+ public void setAuthenticationMethod(@Nullable @NotEmpty final String method) {
+ checkSetterPreconditions();
+
+ final String trimmed = StringSupport.trimOrNull(method);
+ Constraint.isTrue(trimmed == null || Agent.AUTH_METHOD_BASIC.equals(trimmed),
+ "Unsupported authentication method");
+ authenticationMethod = trimmed;
+ }
+
+ /**
+ * Sets whether this loader's agents support cached authentication via cookie.
+ *
+ * <p>Defaults to true.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setSupportsCachedAuthentication(final boolean flag) {
+ checkSetterPreconditions();
+
+ supportsCachedAuthentication = flag;
+ }
+
+ /**
+ * Sets the set of "utility" operations authorized for use by this loader's agents.
+ *
+ * <p>Defaults to empty.</p>
+ *
+ * @param ops authorized operation names
+ */
+ public void setAuthorizedUtilityOperations(@Nullable final Collection<String> ops) {
+ checkSetterPreconditions();
+
+ if (ops != null) {
+ authorizedOperations = CollectionSupport.copyToSet(StringSupport.normalizeStringCollection(ops));
+ } else {
+ authorizedOperations = CollectionSupport.emptySet();
+ }
+ }
+
+ /**
+ * Sets whether this loader's agents are permitted to submit form data for preservation during SSO.
+ *
+ * @param flag flag to set
+ */
+ public void setSupportsPostPreservation(final boolean flag) {
+ checkSetterPreconditions();
+
+ supportsPostPreservation = flag;
+ }
+
+ /**
+ * Sets limit on size of form data to preserve.
+ *
+ * @param limit size limit or 0 for none
+ */
+ public void setPostLimit(@NonNegative final long limit) {
+ checkSetterPreconditions();
+
+ postLimit = Constraint.isGreaterThanOrEqual(0, limit, "Post limit cannot be negative.");
+ }
+
+ /**
+ * Sets the character encoding to apply for POST recovery.
+ *
+ * @param name name of encoding
+ */
+ public void setCharacterEncoding(@Nonnull final String name) {
+ encoding = Charset.forName(name);
+ }
+
+ /**
+ * Sets the identifier of the authenticating authority (IdP, OP, etc.) to use for this loader's agents.
+ *
+ * @param authority authority name
+ */
+ public void setAuthenticatingAuthority(@Nullable @NotEmpty final String authority) {
+ checkSetterPreconditions();
+
+ authenticatingAuthorityLookupStrategy = FunctionSupport.constant(StringSupport.trimOrNull(authority));
+ }
+
+ /**
+ * Sets the discovery service URL to use in the event a session initiator cannot be executed.
+ *
+ * @param url discovery service URL
+ */
+ public void setDiscoveryService(@Nullable final String url) {
+ checkSetterPreconditions();
+
+ discoveryServiceLookupStrategy = FunctionSupport.constant(url);
+ }
+
+ /**
+ * Sets the lookup strategy for the discovery service URL to use in the event a session initiator cannot be executed.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setDiscoveryServiceLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ checkSetterPreconditions();
+
+ discoveryServiceLookupStrategy =
+ Constraint.isNotNull(strategy, "Discovery service lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets the ordered collection of unprefixed session initiator flow IDs to use for this loader's agents.
+ *
+ * @param initiators ordered collection of unprefixed session initiator flow IDs
+ */
+ public void setSessionInitiators(@Nullable final Collection<String> initiators) {
+ checkSetterPreconditions();
+
+ if (initiators != null) {
+ sessionInitiatorLookupStrategy = FunctionSupport.constant(
+ CollectionSupport.copyToList(StringSupport.normalizeStringCollection(initiators)));
+ } else {
+ sessionInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ }
+ }
+
+ /**
+ * Sets the lookup strategy for the ordered collection of unprefixed session initiator flow IDs
+ * to use for this loader's agents.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSessionInitiatorsLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+ checkSetterPreconditions();
+
+ sessionInitiatorLookupStrategy = Constraint.isNotNull(strategy,
+ "Session initiators lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets the ordered collection of unprefixed token consumer flow IDs to use for this loader's agents.
+ *
+ * @param consumers ordered collection of unprefixed token consumer flow IDs
+ */
+ public void setTokenConsumers(@Nullable final Collection<String> consumers) {
+ checkSetterPreconditions();
+
+ if (consumers != null) {
+ tokenConsumerLookupStrategy = FunctionSupport.constant(
+ CollectionSupport.copyToList(StringSupport.normalizeStringCollection(consumers)));
+ } else {
+ tokenConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ }
+ }
+
+ /**
+ * Sets the lookup strategy for the ordered collection of unprefixed token consumer flow IDs
+ * to use for this loader's agents.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setTokenConsumersLookupStrategy(@Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+ checkSetterPreconditions();
+
+ tokenConsumerLookupStrategy = Constraint.isNotNull(strategy, "Token consumers lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets the ordered collection of unprefixed logout initiator flow IDs to use for this loader's agents.
+ *
+ * @param initiators ordered collection of unprefixed logout initiator flow IDs
+ */
+ public void setLogoutInitiators(@Nullable final Collection<String> initiators) {
+ checkSetterPreconditions();
+
+ if (initiators != null) {
+ logoutInitiatorLookupStrategy = FunctionSupport.constant(
+ CollectionSupport.copyToList(StringSupport.normalizeStringCollection(initiators)));
+ } else {
+ logoutInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ }
+ }
+
+ /**
+ * Sets the lookup strategy for the ordered collection of unprefixed logout initiator flow IDs
+ * to use for this loader's agents.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setLogoutInitiatorsLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+ checkSetterPreconditions();
+
+ logoutInitiatorLookupStrategy = Constraint.isNotNull(strategy,
+ "Logout initiators lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets the ordered collection of unprefixed logout consumer flow IDs to use for this loader's agents.
+ *
+ * @param consumers ordered collection of unprefixed logout consumer flow IDs
+ */
+ public void setLogoutConsumers(@Nullable final Collection<String> consumers) {
+ checkSetterPreconditions();
+
+ if (consumers != null) {
+ logoutConsumerLookupStrategy = FunctionSupport.constant(
+ CollectionSupport.copyToList(StringSupport.normalizeStringCollection(consumers)));
+ } else {
+ logoutConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ }
+ }
+
+ /**
+ * Sets the lookup strategy for the ordered collection of unprefixed logout consumer flow IDs
+ * to use for this loader's agents.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setLogoutConsumersLookupStrategy(@Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+ checkSetterPreconditions();
+
+ logoutConsumerLookupStrategy = Constraint.isNotNull(strategy, "Logout consumers lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets the ordered collection of unprefixed admin logout flow IDs to use for this loader's agents.
+ *
+ * @param admins ordered collection of unprefixed admin logout flow IDs
+ */
+ public void setLogoutAdmins(@Nullable final Collection<String> admins) {
+ checkSetterPreconditions();
+
+ if (admins != null) {
+ logoutAdminLookupStrategy = FunctionSupport.constant(
+ CollectionSupport.copyToList(StringSupport.normalizeStringCollection(admins)));
+ } else {
+ logoutAdminLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ }
+ }
+
+ /**
+ * Sets the lookup strategy for the ordered collection of unprefixed admin logout flow IDs
+ * to use for this loader's agents.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setLogoutAdminsLookupStrategy(@Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+ checkSetterPreconditions();
+
+ logoutAdminLookupStrategy = Constraint.isNotNull(strategy, "Logout admins lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the {@link StateManager} to use.
+ *
+ * @param manager state manager
+ */
+ public void setStateManager(@Nonnull final StateManager manager) {
+ checkSetterPreconditions();
+
+ stateManager = Constraint.isNotNull(manager, "StateManager cannot be null");
+ }
+
+ /**
+ * Set the {@link AttributeTranscoderRegistry} to use.
+ *
+ * @param service registry service
+ */
+ public void setAttributeTranscoderRegistry(@Nonnull final ReloadableService<AttributeTranscoderRegistry> service) {
+ checkSetterPreconditions();
+
+ transcodingRegistry = Constraint.isNotNull(service, "AttributeTranscoderRegistry service cannot be null");
+ }
+
+ /**
+ * Set the {@link AttributeResolver} to use.
+ *
+ * @param service resolver service
+ */
+ public void setAttributeResolver(@Nonnull final ReloadableService<AttributeResolver> service) {
+ checkSetterPreconditions();
+
+ attributeResolver = Constraint.isNotNull(service, "AttributeTranscoderRegistry service cannot be null");
+ }
+
+ /**
+ * Set the {@link AttributeFilter} to use.
+ *
+ * @param service filter service
+ */
+ public void setAttributeFilter(@Nonnull final ReloadableService<AttributeFilter> service) {
+ checkSetterPreconditions();
+
+ attributeFilter = Constraint.isNotNull(service, "AttributeFilter service cannot be null");
+ }
+
+ /**
+ * Sets the lookup strategy for the identifier of the authenticating authority (IdP, OP, etc.) to use
+ * for this loader's agents.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAuthenticatingAuthorityLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,String> strategy) {
+ checkSetterPreconditions();
+
+ authenticatingAuthorityLookupStrategy =
+ Constraint.isNotNull(strategy, "Authority lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets a {@link CredentialResolver} to use for resolving keys and certificates "indirectly"
+ * in support of the deprecated older methods.
+ *
+ * <p>This method should not be relied on "publically", and is solely used internally for
+ * backward compatibility.</p>
+ *
+ * @param resolver credential resolver
+ *
+ * @deprecated
+ */
+ @Deprecated(since="1.0.0", forRemoval=true)
+ public void setCredentialResolver(@Nullable final CredentialResolver resolver) {
+ checkSetterPreconditions();
+
+ credentialResolver = resolver;
+ }
+
+ /**
+ * Set the name to use when issuing messages to peers.
+ *
+ * @param issuer name to set
+ */
+ public void setIssuer(@Nullable @NotEmpty final String issuer) {
+ checkSetterPreconditions();
+
+ issuerLookupStrategy = FunctionSupport.constant(issuer);
+ }
+
+ /**
+ * Set a lookup strategy for the name to use when issuing messages to peers.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ checkSetterPreconditions();
+
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /**
+ * Set whether detailed information about errors should be exposed.
+ *
+ * @param flag flag to set
+ */
+ public void setDetailedErrors(final boolean flag) {
+ checkSetterPreconditions();
+
+ detailedErrorsPredicate = PredicateSupport.constant(flag);
+ }
+
+ /**
+ * Set a condition to determine whether detailed information about errors should be exposed.
+ *
+ * @param condition condition to set
+ */
+ public void setDetailedErrorsPredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ checkSetterPreconditions();
+
+ detailedErrorsPredicate = Constraint.isNotNull(condition, "Condition cannot be null");
+ }
+
+ /**
+ * Sets the security configuration for this profile.
+ *
+ * @param configuration security configuration for this profile
+ */
+ public void setSecurityConfiguration(@Nullable final SecurityConfiguration configuration) {
+ checkSetterPreconditions();
+
+ securityConfigurationLookupStrategy = FunctionSupport.constant(configuration);
+ }
+
+ /**
+ * Set a lookup strategy for the security configuration.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSecurityConfigurationLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,SecurityConfiguration> strategy) {
+ securityConfigurationLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the profile configurations for this relying party.
+ *
+ * @param configs the configurations to set
+ */
+ public void setProfileConfigurations(@Nullable final Collection<ProfileConfiguration> configs) {
+ checkSetterPreconditions();
+
+ if (configs == null) {
+ profileConfigurationsLookupStrategy = FunctionSupport.constant(null);
+ } else {
+ final HashMap<String,ProfileConfiguration> map = new HashMap<>();
+ for (final ProfileConfiguration config : configs) {
+ final String trimmedId =
+ Constraint.isNotNull(StringSupport.trimOrNull(config.getId()),
+ "ID of profile configuration class " + config.getClass().getName() + " cannot be null");
+ map.put(trimmedId, config);
+ }
+ profileConfigurationsLookupStrategy = FunctionSupport.constant(map);
+ }
+ }
+
+ /**
+ * Set a lookup strategy for the <code>profileConfigurations</code> property.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProfileConfigurationsLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,Map<String,ProfileConfiguration>> strategy) {
+ checkSetterPreconditions();
+
+ profileConfigurationsLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (stateManager == null) {
+ throw new ComponentInitializationException("StateManager cannot be null");
+ } else if (transcodingRegistry == null) {
+ throw new ComponentInitializationException("AttributeTranscoderRegistry cannot be null");
+ } else if (attributeResolver == null) {
+ throw new ComponentInitializationException("AttributeResolver cannot be null");
+ } else if (attributeFilter == null) {
+ throw new ComponentInitializationException("AttributeFilter cannot be null");
+ }
+
+ if (cacheEnabled) {
+ final CacheBuilder<Object,Object> builder = CacheBuilder.newBuilder();
+ builder.maximumSize(maximumCachedElements);
+ if (expireAfterAccess != null) {
+ if (expireAfterWrite != null) {
+ log.warn("AgentResolver {}: ignoring expireAfterWrite, expireAfterAccess is set", getId());
+ }
+ builder.expireAfterAccess(expireAfterAccess);
+ } else if (expireAfterWrite != null) {
+ builder.expireAfterWrite(expireAfterWrite);
+ } else {
+ throw new ComponentInitializationException("Cache enabled, but expireAfterAccess/expireAfterWrite both null");
+ }
+ resultsCache = builder.build();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public Agent resolveSingle(@Nullable final CriteriaSet criteria) throws ResolverException {
+ checkComponentActive();
+
+ if (criteria == null) {
+ return null;
+ }
+
+ final Cache<CriteriaSet,Agent> localCache = resultsCache;
+
+ // Check cache first if it exists.
+ if (localCache != null) {
+ final Agent cached = localCache.getIfPresent(criteria);
+ if (cached != null) {
+ log.debug("AgentResolver {}: Resolved Agent from cache", getId());
+ return cached;
+ }
+ }
+
+ // Resolve from subclass and build new Agent.
+
+ final AgentTemplate template = doResolve(criteria);
+ if (template == null) {
+ return null;
+ }
+
+ try {
+ final BasicAgent agent = new BasicAgent(protocolSupportManager);
+
+ // Copy in template-only settings.
+ agent.setId(template.getId());
+ agent.setUsernames(template.getUsernames());
+
+ // Copy over loader's configuration.
+
+ if (!template.getAddressRanges().isEmpty()) {
+ agent.setAllowedAddressRanges(template.getAddressRanges());
+ } else {
+ agent.setAllowedAddressRanges(allowedAddressRanges);
+ }
+
+ agent.setAuthenticationMethod(authenticationMethod);
+ agent.setSupportsCachedAuthentication(supportsCachedAuthentication);
+ agent.setAuthorizedUtilityOperations(authorizedOperations);
+ agent.setSupportsPostPreservation(supportsPostPreservation);
+ agent.setPostLimit(postLimit);
+ agent.setCharacterEncoding(encoding.name());
+
+ if (template.getAuthority() != null) {
+ agent.setAuthenticatingAuthority(template.getAuthority());
+ } else {
+ agent.setAuthenticatingAuthorityLookupStrategy(authenticatingAuthorityLookupStrategy);
+ }
+
+ agent.setDiscoveryServiceLookupStrategy(discoveryServiceLookupStrategy);
+ agent.setStateManager(stateManager);
+ agent.setAttributeTranscoderRegistry(transcodingRegistry);
+ agent.setAttributeResolver(attributeResolver);
+ agent.setAttributeFilter(attributeFilter);
+ agent.setCredentialResolver(credentialResolver);
+ if (sessionInitiatorLookupStrategy != null) {
+ agent.setSessionInitiatorsLookupStrategy(sessionInitiatorLookupStrategy);
+ }
+ if (tokenConsumerLookupStrategy != null) {
+ agent.setTokenConsumersLookupStrategy(tokenConsumerLookupStrategy);
+ }
+ if (logoutInitiatorLookupStrategy != null) {
+ agent.setLogoutInitiatorsLookupStrategy(logoutInitiatorLookupStrategy);
+ }
+ if (logoutConsumerLookupStrategy != null) {
+ agent.setLogoutConsumersLookupStrategy(logoutConsumerLookupStrategy);
+ }
+ if (logoutAdminLookupStrategy != null) {
+ agent.setLogoutAdminsLookupStrategy(logoutAdminLookupStrategy);
+ }
+
+ if (template.getIssuer() != null) {
+ agent.setIssuer(template.getIssuer());
+ } else {
+ agent.setIssuerLookupStrategy(issuerLookupStrategy);
+ }
+ agent.setDetailedErrorsPredicate(detailedErrorsPredicate);
+ agent.setSecurityConfigurationLookupStrategy(securityConfigurationLookupStrategy);
+ agent.setProfileConfigurationsLookupStrategy(profileConfigurationsLookupStrategy);
+
+ agent.initialize();
+ return agent;
+
+ } catch (final Exception e) {
+ throw new ResolverException(e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull public Iterable<Agent> resolve(@Nullable final CriteriaSet criteria) throws ResolverException {
+
+ final Agent agent = resolveSingle(criteria);
+ return agent != null ? CollectionSupport.singletonList(agent) : CollectionSupport.emptyList();
+ }
+
+ /**
+ * Performs resolution of a specific named agent record in the form of a template object
+ * which the base class will fully configure and resolve.
+ *
+ * @param criteria resolution criteria
+ *
+ * @return agent template record or null if absent
+ *
+ * @throws ResolverException if an error occurs
+ */
+ @Nullable public abstract AgentTemplate doResolve(@Nonnull final CriteriaSet criteria) throws ResolverException;
+
+ /**
+ * Stub record representing a "partial" {@link Agent} definition that the loader
+ * base class will "wrap" with the settings configured on the loader.
+ */
+ public static class AgentTemplate {
+
+ /** Agent ID. */
+ @Nonnull @NotEmpty private String agentID;
+
+ /** Issuer. */
+ @Nullable @NotEmpty private String issuer;
+
+ /** Authority. */
+ @Nullable @NotEmpty private String authority;
+
+ /** Address ranges. */
+ @Nonnull private Set<IPRange> addressRanges;
+
+ /** Usernames. */
+ @Nonnull private Collection<String> usernames;
+
+ /**
+ * Constructor.
+ *
+ * @param id agent ID
+ * @param iss issuer
+ * @param auth authority
+ * @param ranges address ranges
+ * @param unames usernames
+ *
+ */
+ public AgentTemplate(@Nonnull @NotEmpty final String id, @Nullable @NotEmpty final String iss,
+ @Nullable @NotEmpty final String auth, @Nullable final Set<String> ranges,
+ @Nullable final Collection<String> unames) {
+
+ agentID = Constraint.isNotNull(StringSupport.trimOrNull(id), "ID cannot be null or empty");
+
+ issuer = StringSupport.trimOrNull(iss);
+ authority = StringSupport.trimOrNull(auth);
+
+ if (ranges != null) {
+ addressRanges = new HashSet<>();
+ ranges.stream()
+ .map(IPRange::parseCIDRBlock)
+ .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableSet())).get();
+ } else {
+ addressRanges = CollectionSupport.emptySet();
+ }
+
+ if (unames != null) {
+ usernames = StringSupport.normalizeStringCollection(unames);
+ } else {
+ usernames = CollectionSupport.emptySet();
+ }
+ }
+
+ /**
+ * Get the agent ID.
+ *
+ * @return agent ID
+ */
+ @Nonnull @NotEmpty public String getId() {
+ return agentID;
+ }
+
+ /**
+ * Get the issuer, if set.
+ *
+ * @return issuer or null
+ */
+ @Nullable @NotEmpty public String getIssuer() {
+ return issuer;
+ }
+
+ /**
+ * Get the authenticating authority to use, if set.
+ *
+ * @return authority or null
+ */
+ @Nullable @NotEmpty public String getAuthority() {
+ return authority;
+ }
+
+ /**
+ * Get the allowed address ranges.
+ *
+ * @return allowed address ranges
+ */
+ @Nonnull public Set<IPRange> getAddressRanges() {
+ return addressRanges;
+ }
+
+ /**
+ * Get the alternative usernames.
+ *
+ * @return usernames
+ */
+ @Nonnull public Collection<String> getUsernames() {
+ return usernames;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/DefaultAgentResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/DefaultAgentResolver.java
index ef6244c..b271332 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/DefaultAgentResolver.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/DefaultAgentResolver.java
@@ -16,6 +16,7 @@ package net.shibboleth.sp.impl;
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
@@ -40,9 +41,12 @@ import net.shibboleth.sp.criteria.AgentCredentialCriterion;
*/
public class DefaultAgentResolver extends IdentifiedComponentManager<Agent> implements AgentResolver {
- /** Agent set. */
+ /** Agent map. */
@NonnullAfterInit private Map<String,Agent> agentMap;
+ /** External resolvers to run. */
+ @Nonnull private List<AgentResolver> externalResolvers;
+
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(DefaultAgentResolver.class);
@@ -54,6 +58,24 @@ public class DefaultAgentResolver extends IdentifiedComponentManager<Agent> impl
@Autowired
public DefaultAgentResolver(@Nullable final Collection<Agent> agents) {
super(agents);
+
+ externalResolvers = CollectionSupport.emptyList();
+ }
+
+ /**
+ * Sets a list of external resolvers to query if the in-memory map does not contain
+ * a matching {@link Agent}.
+ *
+ * @param resolvers
+ */
+ public void setExternalResolvers(@Nullable final List<AgentResolver> resolvers) {
+ checkSetterPreconditions();
+
+ if (resolvers != null) {
+ externalResolvers = CollectionSupport.copyToList(resolvers);
+ } else {
+ externalResolvers = CollectionSupport.emptyList();
+ }
}
/** {@inheritDoc} */
@@ -92,11 +114,23 @@ public class DefaultAgentResolver extends IdentifiedComponentManager<Agent> impl
if (criteria != null) {
final AgentCredentialCriterion c_agent = criteria.get(AgentCredentialCriterion.class);
- if (c_agent != null) {
- log.trace("Looking up agent via username: {}", c_agent.getId());
+ if (c_agent != null && agentMap.containsKey(c_agent.getId())) {
+ log.trace("Located internally defined agent via username: {}", c_agent.getId());
return agentMap.get(c_agent.getId());
}
}
+
+ for (final AgentResolver resolver : externalResolvers) {
+ assert resolver != null;
+ try {
+ final Agent agent = resolver.resolveSingle(criteria);
+ if (agent != null) {
+ return agent;
+ }
+ } catch (final ResolverException e) {
+ log.error("External resolver raised exceptoon", e);
+ }
+ }
return null;
}
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/FlatFileExternalAgentResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/FlatFileExternalAgentResolver.java
new file mode 100644
index 0000000..a961c59
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/FlatFileExternalAgentResolver.java
@@ -0,0 +1,357 @@
+/*
+ * 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 net.shibboleth.sp.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.TimerSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.resource.Resource;
+import net.shibboleth.sp.criteria.AgentCredentialCriterion;
+import net.shibboleth.sp.criteria.AgentIDCriterion;
+
+/**
+ * External agent source that relies on a flat file.
+ *
+ * <p>The format of this file is similar to an LDIF file due to the variability of fields supported
+ * and the need for multiple values in some of the fields. Entries are separated by empty lines and
+ * lines that begin with a '#' are comments and ignored.
+ *
+ * Each valid line consists of a field specifier followed by a colon, followed by a single field value.
+ * All values are trimmed of leading and trailing whitespace.
+ *
+ * Supported fields:</p>
+ *
+ * <dl>
+ * <dt>id</dt>
+ * <dd>Agent ID</dd>
+ *
+ * <dt>allowedRange</dt>
+ * <dd>Allowed address range</dd>
+ *
+ * <dt>username</dt>
+ * <dd>An alternative agent username</dd>
+ *
+ * <dt>issuer</dt>
+ * <dd>Issuer value for Agent protocol messages</dd>
+ *
+ * <dt>authority</dt>
+ * <dd>Authenticating authority</dd>
+ * </dl>
+ *
+ * <p>Only the "id" field is required. Both "allowedRange" and "username" may appear any number of times.</p>
+ */
+public class FlatFileExternalAgentResolver extends AbstractExternalAgentResolver {
+
+ /** Agent ID field */
+ @Nonnull @NotEmpty public static final String ID_FIELD = "id:";
+
+ /** IP Range field */
+ @Nonnull @NotEmpty public static final String ALLOWED_RANGE_FIELD = "allowedRange:";
+
+ /** Username field */
+ @Nonnull @NotEmpty public static final String USERNAME_FIELD = "username:";
+
+ /** Issuer field */
+ @Nonnull @NotEmpty public static final String ISSUER_FIELD = "issuer:";
+
+ /** Authority field */
+ @Nonnull @NotEmpty public static final String AUTHORITY_FIELD = "authority:";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(FlatFileExternalAgentResolver.class);
+
+ /** Source of information. */
+ @Nullable private Resource storageResource;
+
+ /** Optional reload interval. */
+ @Nullable private Duration reloadInterval;
+
+ /** File timestamp. */
+ private long lastModified;
+
+ /** Task that monitors resource for reload. */
+ @Nullable private TimerTask reloadTask;
+
+ /** Timer used to schedule reload task. */
+ @Nullable private Timer reloadTaskTimer;
+
+ /** A shared lock to synchronize access. */
+ @NonnullAfterInit private ReadWriteLock lock;
+
+
+ /** Agent map. */
+ @NonnullAfterInit private Map<String,AgentTemplate> agentMap;
+
+ /**
+ * Constructor.
+ *
+ * @param protocolManager protocol support manager
+ */
+ public FlatFileExternalAgentResolver(
+ @ParameterName(name="protocolManager") @Nonnull final ProtocolSupportServiceManager protocolManager) {
+ super(protocolManager);
+
+ lastModified = 0;
+ reloadInterval = Duration.ofMinutes(5);
+ }
+
+ /**
+ * Set the resource to use.
+ *
+ * @param resource resource to use
+ */
+ public void setResource(@Nonnull final Resource resource) {
+ checkSetterPreconditions();
+ storageResource = Constraint.isNotNull(resource, "Resource cannot be null");
+ }
+
+ /**
+ * Sets the time between checks for changes to the resource and reload of the records.
+ *
+ * <p>A value of 0 (the default) indicates that no reloading will be performed. The default is
+ * 5 minutes.</p>
+ *
+ * <p>Non-file resources are detected and reload is not supported.</p>
+ *
+ * @param interval time between reload checking
+ */
+ public void setReloadInterval(@Nonnull final Duration interval) {
+ checkSetterPreconditions();
+
+ Constraint.isNotNull(interval, "Interval cannot be null");
+ Constraint.isFalse(interval.isNegative(), "Interval cannot be negative");
+
+ reloadInterval = interval;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ try {
+ final Resource resource = storageResource;
+ if (resource == null) {
+ throw new ComponentInitializationException("Resource cannot be null");
+ }
+
+ try (final InputStream is = resource.getInputStream()) {
+ agentMap = load(is);
+ }
+
+ if (reloadInterval != null && !reloadInterval.isZero() && resource.isFile()) {
+ lastModified = resource.lastModified();
+
+ assert reloadInterval != null;
+ final long interval = reloadInterval.toMillis();
+
+ lock = new ReentrantReadWriteLock(true);
+ reloadTask = getReloadTask();
+ reloadTaskTimer = new Timer(TimerSupport.getTimerName(this), true);
+ assert reloadTaskTimer != null;
+ reloadTaskTimer.schedule(reloadTask, interval, interval);
+ } else {
+ storageResource = null;
+ }
+
+ } catch (final IOException e) {
+ throw new ComponentInitializationException("Error reading backing resource", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doDestroy() {
+ if (reloadTask != null) {
+ reloadTask.cancel();
+ reloadTask = null;
+ if (reloadTaskTimer != null) {
+ reloadTaskTimer.cancel();
+ }
+ reloadTaskTimer = null;
+ }
+ super.doDestroy();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public AgentTemplate doResolve(@Nonnull final CriteriaSet criteria) throws ResolverException {
+
+ AgentTemplate template = null;
+
+ final Lock readLock = lock.readLock();
+ try {
+ readLock.lock();
+
+ final AgentCredentialCriterion credentialCrit = criteria.get(AgentCredentialCriterion.class);
+ if (credentialCrit != null) {
+ template = agentMap.get(credentialCrit.getId());
+ }
+
+ if (template == null) {
+ final AgentIDCriterion agentCrit = criteria.get(AgentIDCriterion.class);
+ if (agentCrit != null) {
+ template = agentMap.get(agentCrit.getId());
+ }
+ }
+ } finally {
+ readLock.unlock();
+ }
+
+ return template;
+ }
+
+ // Checkstyle: AnonInnerLength OFF
+ @Nonnull protected TimerTask getReloadTask() {
+ return new TimerTask() {
+
+ /** {@inheritDoc} */
+ @Override
+ public void run() {
+ log.debug("Running reload task");
+
+ final Lock writeLock = lock.writeLock();
+
+ try {
+ writeLock.lock();
+
+ final Resource resource = storageResource;
+
+ try {
+ if (resource != null && resource.isFile() && resource.exists()) {
+ final long modTime = resource.lastModified();
+ if (modTime > lastModified) {
+ log.info("Resource {} modified, attempting reload", resource);
+ try (final InputStream is = resource.getInputStream()) {
+ agentMap = load(is);
+ }
+ lastModified = modTime;
+ } else {
+ log.info("Resource {} not modified, skipping reload", resource);
+ }
+ } else {
+ log.warn("Non-file resource, reload monitoring not supported");
+ }
+ } catch (final IOException e) {
+ log.error("Error reloading records", e);
+ }
+
+ } finally {
+ writeLock.unlock();
+ }
+ }
+ };
+ }
+// Checkstyle: AnonInnerLength ON
+
+ /**
+ * Loads data from the supplied input stream.
+ *
+ * <p>Must be called while holding the write lock.</p>
+ *
+ * @param is
+ *
+ * @return the constructed map of records
+ *
+ * @throws IOException on error
+ */
+ @Nonnull @Unmodifiable @NotLive private Map<String,AgentTemplate> load(
+ @Nonnull final InputStream is) throws IOException {
+
+ final Map<String,AgentTemplate> theMap = new HashMap<>();
+
+ try (final BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+
+ int lineno = 0;
+ String line;
+
+ String id = null;
+ String issuer = null;
+ String authority = null;
+ final Set<String> ranges = new HashSet<>();
+ final Collection<String> usernames = new HashSet<>();
+
+
+ while ((line = reader.readLine()) != null) {
+ ++lineno;
+
+ if (line.isEmpty() || line.isBlank()) {
+ if (id != null) {
+ theMap.put(id, new AgentTemplate(id, issuer, authority, ranges, usernames));
+ }
+
+ id = null;
+ issuer = null;
+ authority = null;
+ ranges.clear();
+ usernames.clear();
+ continue;
+ } else if (line.startsWith("#")) {
+ continue;
+ } else if (line.startsWith(ID_FIELD)) {
+ id = line.substring(ID_FIELD.length());
+ } else if (line.startsWith(ALLOWED_RANGE_FIELD)) {
+ ranges.add(line.substring(ALLOWED_RANGE_FIELD.length()));
+ } else if (line.startsWith(USERNAME_FIELD)) {
+ usernames.add(line.substring(USERNAME_FIELD.length()));
+ } else if (line.startsWith(ISSUER_FIELD)) {
+ issuer = line.substring(ISSUER_FIELD.length());
+ } else if (line.startsWith(AUTHORITY_FIELD)) {
+ authority = line.substring(AUTHORITY_FIELD.length());
+ } else {
+ log.warn("Invalid line ({}) found", lineno);
+ }
+ }
+
+ if (id != null) {
+ theMap.put(id, new AgentTemplate(id, issuer, authority, ranges, usernames));
+ }
+
+ }
+
+ return theMap;
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list