[java-plugin-shibd] branch main updated: WIP on storage-based credential resolvers.
Codeberg
noreply at shibboleth.net
Mon Aug 24 18:54:40 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/ccd8e0434f9dbc8a397d247307d96ff332cc95c1
The following commit(s) were added to refs/heads/main by this push:
new ccd8e04 WIP on storage-based credential resolvers.
ccd8e04 is described below
commit ccd8e0434f9dbc8a397d247307d96ff332cc95c1
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Mon Aug 24 14:54:24 2026 -0400
WIP on storage-based credential resolvers.
---
sp-server-api/pom.xml | 6 +
.../AbstractOrderedCredentialResolver.java | 2 +-
.../AbstractStorageServiceCredentialResolver.java | 373 +++++++++++++++++++++
sp-server-impl/pom.xml | 1 -
.../credential/impl/StaticCredentialResolver.java | 2 +-
.../impl/StaticMapCredentialResolver.java | 2 +-
.../impl/X509CredentialStorageServiceResolver.java | 195 +++++++++++
.../sp/storage/impl/FilesystemStorageService.java | 7 +-
.../storage/impl/FilesystemStorageServiceTest.java | 2 +-
9 files changed, 584 insertions(+), 6 deletions(-)
diff --git a/sp-server-api/pom.xml b/sp-server-api/pom.xml
index b97fefc..afaddf3 100644
--- a/sp-server-api/pom.xml
+++ b/sp-server-api/pom.xml
@@ -84,6 +84,12 @@
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
<dependency>
<groupId>${slf4j.groupId}</groupId>
<artifactId>jcl-over-slf4j</artifactId>
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractOrderedCredentialResolver.java b/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractOrderedCredentialResolver.java
index 91f1034..f66a4fc 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractOrderedCredentialResolver.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractOrderedCredentialResolver.java
@@ -185,7 +185,7 @@ public abstract class AbstractOrderedCredentialResolver extends AbstractIdentifi
*
* @throws ResolverException if an error occurs
*/
- @Nonnull public abstract Iterable<Credential> doResolve(@Nullable final CriteriaSet criteria)
+ @Nonnull protected abstract Iterable<Credential> doResolve(@Nullable final CriteriaSet criteria)
throws ResolverException;
/**
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java b/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java
new file mode 100644
index 0000000..0845e58
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java
@@ -0,0 +1,373 @@
+/*
+ * 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.credential;
+
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.opensaml.core.criterion.EntityIdCriterion;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.criterion.ProfileRequestContextCriterion;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.storage.StorageService;
+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.codec.StringDigester;
+import net.shibboleth.shared.codec.StringDigester.OutputFormat;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.ClassCriterion;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.Criterion;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.velocity.Template;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.AgentIDCriterion;
+import net.shibboleth.sp.Application;
+import net.shibboleth.sp.ApplicationIDCriterion;
+
+/**
+ * A dymamic resolver that relies on a {@link StorageService} to retrieve blobs
+ * of information that its subclasses will understand how to turn into a
+ * specifically known type of {@link Credential}.
+ *
+ * <p>Typically more than one object from storage will be required to construct
+ * the expected type of {@link Credential}, and subclasses are expected to
+ * govern exactly how that occurs.</p>
+ *
+ * <p>The resolver functions by evaluating a Velocity Template with information
+ * pulled from the supplied {@link CriteriaSet} to allow dynamic construction
+ * of the storage context and key to retrieve. The resulting values are
+ * passed to the subclass to further process into specific lookup values
+ * to retrieve.</p>
+ *
+ * <p>The resolver recognizes these {@link Criterion} types natively and
+ * supplies them as input to the template:</p>
+ *
+ * <ul>
+ * <li>{@link ProfileRequestContextCriterion} - generic extension point</li>
+ * <li>{@link AgentIDCriterion} - used for {@link Agent}-based lookup
+ * <li>{@link ApplicationIDCriterion} - used for {@link Application}-based lookup
+ * <li>{@link EntityIdCriterion} - used for relying party-based lookup</li>
+ * <li>{@link UsageCriterion} - used for usage-based lookup</li>
+ * </ul>
+ *
+ * <p>In addition, the entire {@link CriteriaSet} is also supplied as an extension point, and
+ * if supplied, the entityID is transformed by a pluggable function, by default into a SHA-1
+ * hash that is hex-encoded in lower case.</p>
+ *
+ * <p>Note that subclasses are required to perform evaluation of intended usage based on
+ * the {@link UsageCriterion} such that any resolved credentials are assumed to either
+ * match the caller's expectations or not carry any usage designation at all.</p>
+ *
+ * TODO: implement caching layer here or in subclasses somehow
+ *
+ * @param <T> the type of credential supported
+ */
+public abstract class AbstractStorageServiceCredentialResolver<T extends Credential> extends AbstractOrderedCredentialResolver {
+
+ /** Default prefix for default context value. */
+ @Nonnull @NotEmpty public static final String DEFAULT_CONTEXT_PREFIX = "net.shibboleth.sp.credential.";
+
+ /** The Velocity context variable name for the Agent ID. */
+ @Nonnull @NotEmpty public static final String CONTEXT_KEY_CRITERIA = "criteria";
+
+ /** The Velocity context variable name for the Agent ID. */
+ @Nonnull @NotEmpty public static final String CONTEXT_KEY_AGENT_ID = "agentID";
+
+ /** The Velocity context variable name for the Agent ID. */
+ @Nonnull @NotEmpty public static final String CONTEXT_KEY_APPLICATION_ID = "applicationID";
+
+ /** The Velocity context variable name for the entity ID. */
+ @Nonnull @NotEmpty public static final String CONTEXT_KEY_ENTITY_ID = "entityID";
+
+ /** The Velocity context variable name for the transformed entity ID. */
+ @Nonnull @NotEmpty public static final String CONTEXT_KEY_TX_ENTITY_ID = "transformedEntityID";
+
+ /** The Velocity context variable name for the {@link ProfileRequestContext}. */
+ @Nonnull @NotEmpty public static final String CONTEXT_KEY_PRC = "profileRequestContext";
+
+ /** The Velocity context variable name for the {@link UsageType}. */
+ @Nonnull @NotEmpty public static final String CONTEXT_KEY_USAGE = "usage";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractStorageServiceCredentialResolver.class);
+
+ /** Credential type. */
+ @Nonnull private final Class<T> credentialType;
+
+ /** Storage service to use. */
+ @NonnullAfterInit private StorageService storageService;
+
+ /** Velocity engine to use. */
+ @NonnullAfterInit private VelocityEngine velocityEngine;
+
+ /** Function to transform the entityID. */
+ @NonnullAfterInit private Function<String,String> entityIDTransformStrategy;
+
+ /** Velocity template source for context. */
+ @NonnullAfterInit private String contextTemplateString;
+
+ /** Velocity template source for key. */
+ @NonnullAfterInit private String keyTemplateString;
+
+ /** Velocity template for context. */
+ @NonnullAfterInit private Template contextTemplate;
+
+ /** Velocity template for key. */
+ @NonnullAfterInit private Template keyTemplate;
+
+ /**
+ * Constructor.
+ *
+ * @param type class type of generic parameter
+ */
+ public AbstractStorageServiceCredentialResolver(@Nonnull @ParameterName(name="type") final Class<T> type) {
+ credentialType = Constraint.isNotNull(type, "Credential type cannot be null");
+
+ }
+
+ /**
+ * Gets the {@link StorageService} to use.
+ *
+ * @return storage service
+ */
+ @NonnullAfterInit public StorageService getStorageService() {
+ return storageService;
+ }
+
+ /**
+ * Sets the {@link StorageService} to use.
+ *
+ * @param storage storage service
+ */
+ public void setStorageService(@Nonnull final StorageService storage) {
+ checkSetterPreconditions();
+
+ storageService = Constraint.isNotNull(storage, "StorageService cannot be null");
+ }
+
+ /**
+ * Sets the {@link VelocityEngine} to use.
+ *
+ * @param engine Velocity engine
+ */
+ public void setVelocityEngine(@Nonnull final VelocityEngine engine) {
+ checkSetterPreconditions();
+
+ velocityEngine = Constraint.isNotNull(engine, "VelocityEngine cannot be null");
+ }
+
+ /**
+ * Sets the transform to apply to the entityID if supplied.
+ *
+ * <p>Defaults to a SHA-1 hash as a hex-encoded lower-case string.</p>
+ *
+ * @param strategy transform strategy
+ */
+ public void setEntityIDTransformStrategy(@Nonnull final Function<String,String> strategy) {
+ checkSetterPreconditions();
+
+ entityIDTransformStrategy = Constraint.isNotNull(strategy, "EntityID transform strategy cannot be null");
+ }
+
+ /**
+ * Sets the Velocity template to use to construct the storage context.
+ *
+ * <p>Defaults to "net.shibboleth.sp.credential.resolver.id" where "id" is the component ID.</p>
+ *
+ * @param template template for storage context
+ */
+ public void setContextTemplate(@Nonnull final String template) {
+ checkSetterPreconditions();
+
+ contextTemplateString = Constraint.isNotNull(template, "Storage context template cannot be null");
+ }
+
+ /**
+ * Sets the Velocity template to use to construct the storage key.
+ *
+ * @param template template for storage key
+ */
+ public void setKeyTemplate(@Nonnull final String template) {
+ checkSetterPreconditions();
+
+ contextTemplateString = Constraint.isNotNull(template, "Storage key template cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (storageService == null) {
+ throw new ComponentInitializationException("StorageService cannot be null");
+ } else if (velocityEngine == null) {
+ throw new ComponentInitializationException("VelocityEngine cannot be null");
+ } else if (keyTemplateString == null) {
+ throw new ComponentInitializationException("Storage key template cannot be null");
+ }
+
+ keyTemplate = Template.fromTemplate(velocityEngine, keyTemplateString, StandardCharsets.UTF_8);
+
+ if (contextTemplateString == null) {
+ contextTemplateString = DEFAULT_CONTEXT_PREFIX + ensureId();
+ }
+
+ contextTemplate = Template.fromTemplate(velocityEngine, contextTemplateString, StandardCharsets.UTF_8);
+
+ if (entityIDTransformStrategy == null) {
+ entityIDTransformStrategy = new DefaultTransformStrategy();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull @NotLive @Unmodifiable protected Iterable<Credential> doResolve(@Nullable final CriteriaSet criteria)
+ throws ResolverException {
+
+ if (criteria != null) {
+ // Check type.
+ final ClassCriterion<?> classCriterion = criteria.get(ClassCriterion.class);
+ if (classCriterion != null && !classCriterion.getClass().isAssignableFrom(credentialType)) {
+ log.debug("{}: ClassCriterion specified {}, not compatible with this resolver", getId(),
+ classCriterion.getClass());
+ return CollectionSupport.emptyList();
+ }
+ }
+
+ final VelocityContext ctx = populateVelocityContext(criteria);
+
+ final String storageContext = contextTemplate.merge(ctx);
+ final String storageKey = keyTemplate.merge(ctx);
+
+ log.debug("{}: Resolved storage context ({}), key ({})", getId(), storageContext, storageKey);
+
+ return doResolve(criteria, storageContext, storageKey);
+ }
+
+ /**
+ * Populate and return a {@link VelocityContext} with the necessary criteria information.
+ *
+ * @param criteria input to resolver
+ *
+ * @return the populated context
+ */
+ @Nonnull protected VelocityContext populateVelocityContext(@Nullable final CriteriaSet criteria) {
+ final VelocityContext ctx = new VelocityContext();
+
+ if (criteria == null) {
+ return ctx;
+ }
+ ctx.put(CONTEXT_KEY_CRITERIA, criteria);
+
+
+ final ProfileRequestContextCriterion prcCrit = criteria.get(ProfileRequestContextCriterion.class);
+ if (prcCrit != null) {
+ ctx.put(CONTEXT_KEY_PRC, prcCrit.getProfileRequestContext());
+ }
+
+ final AgentIDCriterion agentCrit = criteria.get(AgentIDCriterion.class);
+ if (agentCrit != null) {
+ ctx.put(CONTEXT_KEY_AGENT_ID, agentCrit.getId());
+ }
+
+ final ApplicationIDCriterion appCrit = criteria.get(ApplicationIDCriterion.class);
+ if (appCrit != null) {
+ ctx.put(CONTEXT_KEY_APPLICATION_ID, appCrit.getId());
+ }
+
+ final EntityIdCriterion entityIDCrit = criteria.get(EntityIdCriterion.class);
+ if (entityIDCrit != null) {
+ ctx.put(CONTEXT_KEY_ENTITY_ID, entityIDCrit.getEntityId());
+ ctx.put(CONTEXT_KEY_TX_ENTITY_ID, entityIDTransformStrategy.apply(entityIDCrit.getEntityId()));
+ }
+
+ final UsageCriterion usageCrit = criteria.get(UsageCriterion.class);
+ if (usageCrit != null) {
+ ctx.put(CONTEXT_KEY_USAGE, usageCrit.getUsage());
+ } else {
+ ctx.put(CONTEXT_KEY_USAGE, UsageType.UNSPECIFIED);
+ }
+
+ return ctx;
+ }
+
+ /**
+ * Perform the resolution as required for the specific type of {@link Credential}.
+ *
+ * <p>Subclasses implement this by taking the supplied information and adjusting it if required to
+ * perform one or more storage lookups for information needed to construct the resolved credential(s).</p>
+ *
+ * <p>Most often, a single {@link Credential} (at most) will be returned as it is complex to store
+ * the information needed for multiple objects without some fairly exotic tricks, but this is left
+ * to the subclass to decide.</p>
+ *
+ * @param criteria input to resolver
+ * @param storageContext evaluated storage context
+ * @param storageKey evaluated storage key
+ *
+ * @return zero or more resolved credentials
+ *
+ * @throws ResolverException on errors, generally wrapped from storage service
+ */
+ @Nonnull @NotLive @Unmodifiable protected abstract Iterable<Credential> doResolve(
+ @Nullable final CriteriaSet criteria, @Nonnull final String storageContext,
+ @Nonnull final String storageKey) throws ResolverException;
+
+ /**
+ * Defaults to SHA-1 hash.
+ */
+ private static class DefaultTransformStrategy implements Function<String,String> {
+
+ /** String digester. */
+ @Nonnull private final StringDigester digester;
+
+ /**
+ * Constructor.
+ *
+ * @throws ComponentInitializationException on failure
+ */
+ public DefaultTransformStrategy() throws ComponentInitializationException {
+ try {
+ digester = new StringDigester("SHA1", OutputFormat.HEX_LOWER);
+ } catch (final NoSuchAlgorithmException e) {
+ throw new ComponentInitializationException(e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public String apply(@Nullable final String t) {
+ return digester.apply(t);
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/pom.xml b/sp-server-impl/pom.xml
index 05cd9b2..e3767ad 100644
--- a/sp-server-impl/pom.xml
+++ b/sp-server-impl/pom.xml
@@ -147,7 +147,6 @@
<scope>provided</scope>
</dependency>
-
<!-- Test Dependencies -->
<dependency>
<groupId>${project.groupId}</groupId>
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticCredentialResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticCredentialResolver.java
index 95643af..1150627 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticCredentialResolver.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticCredentialResolver.java
@@ -51,7 +51,7 @@ public class StaticCredentialResolver extends AbstractOrderedCredentialResolver
/** {@inheritDoc} */
@Override
- @Nonnull @NotLive @Unmodifiable public Iterable<Credential> doResolve(@Nullable final CriteriaSet criteria) throws ResolverException {
+ @Nonnull @NotLive @Unmodifiable protected Iterable<Credential> doResolve(@Nullable final CriteriaSet criteria) throws ResolverException {
return creds;
}
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticMapCredentialResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticMapCredentialResolver.java
index 509190b..6490fc3 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticMapCredentialResolver.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticMapCredentialResolver.java
@@ -155,7 +155,7 @@ public class StaticMapCredentialResolver extends AbstractOrderedCredentialResolv
/** {@inheritDoc} */
@Override
- @Nonnull @NotLive @Unmodifiable public Iterable<Credential> doResolve(@Nullable final CriteriaSet criteria)
+ @Nonnull @NotLive @Unmodifiable protected Iterable<Credential> doResolve(@Nullable final CriteriaSet criteria)
throws ResolverException {
// Extract known criteria.
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java
new file mode 100644
index 0000000..ae86610
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java
@@ -0,0 +1,195 @@
+/*
+ * 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.credential.impl;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.x509.X509Credential;
+import org.opensaml.spring.credential.BasicX509CredentialFactoryBean;
+import org.opensaml.storage.StorageRecord;
+import org.slf4j.Logger;
+import org.springframework.core.io.ByteArrayResource;
+
+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.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.spring.resource.ResourceHelper;
+import net.shibboleth.sp.credential.AbstractStorageServiceCredentialResolver;
+
+/**
+ * A specialized storage-based resolver that can resolve the {@link X509Credential} type.
+ *
+ * <p>Additional settings are required to specify how to locate the various components such as
+ * the private key and certificate(s). A key password may be configured but must be usable for any
+ * resolved keys.</p>
+ *
+ * <p>The private key and certificate(s) must be stored separately for this implementation in DER or PEM
+ * fornat but cannot be binary-encoded.</p>
+ */
+public class X509CredentialStorageServiceResolver extends AbstractStorageServiceCredentialResolver<X509Credential> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(X509CredentialStorageServiceResolver.class);
+
+ /** Suffix added to storage key when searching for the private key. */
+ @Nonnull private String privateKeySuffix;
+
+ /** Suffix added to storage key when searching for the public key certificate(s). */
+ @Nonnull private String certificateSuffix;
+
+ /** Suffix added to storage key when searching for the CRL(s). */
+ @Nullable private String crlSuffix;
+
+ /** Password for private key(s). */
+ @Nullable private char[] privateKeyPassword;
+
+ /**
+ * Constructor.
+ */
+ public X509CredentialStorageServiceResolver() {
+ super(X509Credential.class);
+
+ privateKeySuffix = ".key";
+ certificateSuffix = ".crt";
+ }
+
+ /**
+ * Sets the storage key suffix to add when resolving the private key.
+ *
+ * <p>Defaults to ".key"</p>
+ *
+ * @param suffix suffix to add
+ */
+ public void setPrivateKeySuffix(@Nonnull @NotEmpty final String suffix) {
+ checkSetterPreconditions();
+
+ privateKeySuffix = Constraint.isNotNull(StringSupport.trimOrNull(suffix),
+ "Private key suffix cannot be null or empty");
+ }
+
+ /**
+ * Sets the storage key suffix to add when resolving the certificate(s).
+ *
+ * <p>Defaults to ".crt"</p>
+ *
+ * @param suffix suffix to add
+ */
+ public void setCertificateSuffix(@Nonnull @NotEmpty final String suffix) {
+ checkSetterPreconditions();
+
+ certificateSuffix = Constraint.isNotNull(StringSupport.trimOrNull(suffix),
+ "Certificate suffix cannot be null or empty");
+ }
+
+ /**
+ * Sets the storage key suffix to add when resolving the CRL(s).
+ *
+ * <p>Defaults to null, which skips attempted resolution of CRLs.</p>
+ *
+ * @param suffix suffix to add
+ */
+ public void setCRLSuffix(@Nullable @NotEmpty final String suffix) {
+ checkSetterPreconditions();
+
+ crlSuffix = StringSupport.trimOrNull(suffix);
+ }
+
+ /**
+ * Sets the private key password to apply to any resolved keys.
+ *
+ * @param password key password
+ */
+ public void setPrivateKeyPassword(@Nullable final String password) {
+ checkSetterPreconditions();
+
+ privateKeyPassword = password != null ? password.toCharArray() : null;
+ }
+
+ /** {@inheritDoc}
+ * @throws ResolverException */
+ @Override
+ @Nonnull @NotLive @Unmodifiable protected Iterable<Credential> doResolve(
+ @Nullable final CriteriaSet criteria, @Nonnull final String storageContext,
+ @Nonnull final String storageKey) throws ResolverException {
+
+ final StorageRecord<String> privateKeyData;
+ final StorageRecord<String> certificateData;
+ final StorageRecord<String> crlData;
+
+ try {
+ privateKeyData = getStorageService().read(storageContext, storageKey + privateKeySuffix);
+ certificateData = getStorageService().read(storageContext, storageKey + certificateSuffix);
+ if (crlSuffix != null) {
+ crlData = getStorageService().read(storageContext, storageKey + crlSuffix);
+ } else {
+ crlData = null;
+ }
+
+ } catch (final IOException e) {
+ throw new ResolverException("I/O exception retrieving credential data", e);
+ }
+
+ if (privateKeyData == null && certificateData == null) {
+ log.debug("{}: No private key or certificate data resolved", getId());
+ return CollectionSupport.emptyList();
+ }
+
+ // Populate a factory bean from the resolved material.
+
+ final BasicX509CredentialFactoryBean factory = new BasicX509CredentialFactoryBean();
+ factory.setPrivateKeyPassword(privateKeyPassword);
+ if (privateKeyData != null) {
+ factory.setPrivateKeyResource(
+ ResourceHelper.of(new ByteArrayResource(
+ privateKeyData.getValue().getBytes(StandardCharsets.UTF_8))));
+ }
+ if (certificateData != null) {
+ factory.setCertificateResource(
+ ResourceHelper.of(new ByteArrayResource(
+ certificateData.getValue().getBytes(StandardCharsets.UTF_8))));
+ }
+ if (crlData != null) {
+ factory.setCRLs(
+ CollectionSupport.singletonList(
+ ResourceHelper.of(new ByteArrayResource(
+ crlData.getValue().getBytes(StandardCharsets.UTF_8)))));
+ }
+
+ try {
+ final X509Credential credential = factory.getObject();
+ if (credential != null) {
+ log.debug("{}: Resolved X509Credential for caller", getId());
+ return CollectionSupport.singletonList(credential);
+ }
+
+ log.warn("{}: Factory bean returned null", getId());
+ return CollectionSupport.emptyList();
+ } catch (final Exception e) {
+ throw new ResolverException("Exception during creation of X509Credential object", e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/storage/impl/FilesystemStorageService.java b/sp-server-impl/src/main/java/net/shibboleth/sp/storage/impl/FilesystemStorageService.java
index f5f7bea..598c15d 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/storage/impl/FilesystemStorageService.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/storage/impl/FilesystemStorageService.java
@@ -50,6 +50,11 @@ public class FilesystemStorageService extends AbstractStorageService {
/** Base location of storage tree in filesystem. */
@NonnullAfterInit private String storageBase;
+ /** Constructor. */
+ public FilesystemStorageService() {
+ readOnly = true;
+ }
+
/**
* Sets whether the file system is clustered.
*
@@ -66,7 +71,7 @@ public class FilesystemStorageService extends AbstractStorageService {
/**
* Sets whether the service should operate read-only.
*
- * <p>Defaults to false.</p>
+ * <p>Defaults to true.</p>
*
* @param flag flag to set
*/
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/storage/impl/FilesystemStorageServiceTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/storage/impl/FilesystemStorageServiceTest.java
index f0df889..7749029 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/storage/impl/FilesystemStorageServiceTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/storage/impl/FilesystemStorageServiceTest.java
@@ -226,7 +226,7 @@ public class FilesystemStorageServiceTest {
*
* @throws ComponentInitializationException
*/
- @Nonnull private FilesystemStorageService getService(final boolean readOnly)
+ @Nonnull private StorageService getService(final boolean readOnly)
throws ComponentInitializationException {
final FilesystemStorageService ss = new FilesystemStorageService();
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list