[java-plugin-shibd] branch main updated: JSHIBD-25 - Develop necessary CredentialResolvers for SP service
Codeberg
noreply at shibboleth.net
Tue Aug 25 15:49:41 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/d4979c733d037cd63056c41ccb5e1ca87c0f4d07
The following commit(s) were added to refs/heads/main by this push:
new d4979c7 JSHIBD-25 - Develop necessary CredentialResolvers for SP service
d4979c7 is described below
commit d4979c733d037cd63056c41ccb5e1ca87c0f4d07
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue Aug 25 11:49:26 2026 -0400
JSHIBD-25 - Develop necessary CredentialResolvers for SP service
https://shibboleth.atlassian.net/browse/JSHIBD-25
Adjustments to resolver class responsibilities for Velocity templates.
Fix factory bean usage.
---
.../AbstractStorageServiceCredentialResolver.java | 71 ++++++++++------
.../impl/X509CredentialStorageServiceResolver.java | 96 +++++++++++++++-------
2 files changed, 111 insertions(+), 56 deletions(-)
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
index 0845e58..be05604 100644
--- 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
@@ -16,6 +16,7 @@ package net.shibboleth.sp.credential;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
+import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -23,6 +24,7 @@ import javax.annotation.Nullable;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.criterion.ProfileRequestContextCriterion;
@@ -135,14 +137,11 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
/** 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;
+
+ /** Map of usage types to template values. */
+ @Nonnull private Map<UsageType,String> usageMap;
/**
* Constructor.
@@ -151,7 +150,8 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
*/
public AbstractStorageServiceCredentialResolver(@Nonnull @ParameterName(name="type") final Class<T> type) {
credentialType = Constraint.isNotNull(type, "Credential type cannot be null");
-
+ usageMap = Map.of(UsageType.SIGNING, "-signing", UsageType.ENCRYPTION, "-encryption",
+ UsageType.UNSPECIFIED, "-signing");
}
/**
@@ -174,6 +174,15 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
storageService = Constraint.isNotNull(storage, "StorageService cannot be null");
}
+ /**
+ * Gets the {@link VelocityEngine} to use.
+ *
+ * @return Velocity engine
+ */
+ @NonnullAfterInit public VelocityEngine getVelocityEngine() {
+ return velocityEngine;
+ }
+
/**
* Sets the {@link VelocityEngine} to use.
*
@@ -210,16 +219,24 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
contextTemplateString = Constraint.isNotNull(template, "Storage context template cannot be null");
}
-
+
/**
- * Sets the Velocity template to use to construct the storage key.
+ * Sets a map from {@link UsageType} to string token, allowing customizable injection of a string
+ * into the Velocity context to abbreviate or otherwise control what to resolve based on a supplied
+ * {@link UsageCriterion}.
+ *
+ * <p>Defaults to "-signing", "-encryption", and "-signing" for {@link UsageType#SIGNING},
+ * {@link UsageType#ENCRYPTION}, and {@link UsageType#UNSPECIFIED} respectively, which is
+ * compatible with the advisable step of separating keys by usage.</p>
*
- * @param template template for storage key
+ * @param map map of usage types to string values
*/
- public void setKeyTemplate(@Nonnull final String template) {
- checkSetterPreconditions();
-
- contextTemplateString = Constraint.isNotNull(template, "Storage key template cannot be null");
+ public void setUsageMap(@Nullable final Map<UsageType,String> map) {
+ if (map != null) {
+ usageMap = CollectionSupport.copyToMap(map);
+ } else {
+ usageMap = CollectionSupport.emptyMap();
+ }
}
/** {@inheritDoc} */
@@ -231,12 +248,8 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
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();
}
@@ -265,12 +278,16 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
final VelocityContext ctx = populateVelocityContext(criteria);
- final String storageContext = contextTemplate.merge(ctx);
- final String storageKey = keyTemplate.merge(ctx);
+ final String storageContext;
+ try {
+ storageContext = contextTemplate.merge(ctx);
+ } catch (final VelocityException e) {
+ throw new ResolverException("Error executing context template", e);
+ }
- log.debug("{}: Resolved storage context ({}), key ({})", getId(), storageContext, storageKey);
+ log.debug("{}: Resolved storage context ({})", getId(), storageContext);
- return doResolve(criteria, storageContext, storageKey);
+ return doResolve(ctx, criteria, storageContext);
}
/**
@@ -312,9 +329,9 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
final UsageCriterion usageCrit = criteria.get(UsageCriterion.class);
if (usageCrit != null) {
- ctx.put(CONTEXT_KEY_USAGE, usageCrit.getUsage());
+ ctx.put(CONTEXT_KEY_USAGE, usageMap.get(usageCrit.getUsage()));
} else {
- ctx.put(CONTEXT_KEY_USAGE, UsageType.UNSPECIFIED);
+ ctx.put(CONTEXT_KEY_USAGE, usageMap.get(UsageType.UNSPECIFIED));
}
return ctx;
@@ -330,17 +347,17 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
* the information needed for multiple objects without some fairly exotic tricks, but this is left
* to the subclass to decide.</p>
*
+ * @param velocityContext Velocity context constructed from criteria
* @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;
+ @Nonnull final VelocityContext velocityContext, @Nullable final CriteriaSet criteria,
+ @Nonnull final String storageContext) throws ResolverException;
/**
* Defaults to SHA-1 hash.
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
index ae86610..b89deea 100644
--- 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
@@ -20,6 +20,8 @@ import java.nio.charset.StandardCharsets;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.exception.VelocityException;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.x509.X509Credential;
import org.opensaml.spring.credential.BasicX509CredentialFactoryBean;
@@ -27,16 +29,19 @@ import org.opensaml.storage.StorageRecord;
import org.slf4j.Logger;
import org.springframework.core.io.ByteArrayResource;
+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.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
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.shared.velocity.Template;
import net.shibboleth.sp.credential.AbstractStorageServiceCredentialResolver;
/**
@@ -55,16 +60,25 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
@Nonnull private final Logger log = LoggerFactory.getLogger(X509CredentialStorageServiceResolver.class);
/** Suffix added to storage key when searching for the private key. */
- @Nonnull private String privateKeySuffix;
+ @Nonnull private String privateKeyTemplateString;
/** Suffix added to storage key when searching for the public key certificate(s). */
- @Nonnull private String certificateSuffix;
+ @Nonnull private String certificateTemplateString;
/** Suffix added to storage key when searching for the CRL(s). */
- @Nullable private String crlSuffix;
+ @Nullable private String crlTemplateString;
/** Password for private key(s). */
@Nullable private char[] privateKeyPassword;
+
+ /** Velocity template for private key storage key. */
+ @NonnullAfterInit Template privateKeyTemplate;
+
+ /** Velocity template for certificate storage key. */
+ @NonnullAfterInit Template certificateTemplate;
+
+ /** Velocity template for CRL storage key. */
+ @NonnullAfterInit Template crlTemplate;
/**
* Constructor.
@@ -72,49 +86,49 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
public X509CredentialStorageServiceResolver() {
super(X509Credential.class);
- privateKeySuffix = ".key";
- certificateSuffix = ".crt";
+ privateKeyTemplateString = "sp${usage}.key";
+ certificateTemplateString = "sp${usage}.crt";
}
/**
- * Sets the storage key suffix to add when resolving the private key.
+ * Sets the Velocity template string for the private key's storage key.
*
- * <p>Defaults to ".key"</p>
+ * <p>Defaults to "sp$usage.key", which punts any other decoration to the storage context.</p>
*
- * @param suffix suffix to add
+ * @param template template string
*/
- public void setPrivateKeySuffix(@Nonnull @NotEmpty final String suffix) {
+ public void setPrivateKeyTemplate(@Nonnull @NotEmpty final String template) {
checkSetterPreconditions();
- privateKeySuffix = Constraint.isNotNull(StringSupport.trimOrNull(suffix),
- "Private key suffix cannot be null or empty");
+ privateKeyTemplateString = Constraint.isNotNull(StringSupport.trimOrNull(template),
+ "Private key template cannot be null or empty");
}
/**
- * Sets the storage key suffix to add when resolving the certificate(s).
+ * Sets the Velocity template string for the certificate's storage key.
*
- * <p>Defaults to ".crt"</p>
+ * <p>Defaults to "sp$usage.crt", which punts any other decoration to the storage context.</p>
*
- * @param suffix suffix to add
+ * @param template template string
*/
- public void setCertificateSuffix(@Nonnull @NotEmpty final String suffix) {
+ public void setCertificateTemplate(@Nonnull @NotEmpty final String template) {
checkSetterPreconditions();
- certificateSuffix = Constraint.isNotNull(StringSupport.trimOrNull(suffix),
- "Certificate suffix cannot be null or empty");
+ certificateTemplateString = Constraint.isNotNull(StringSupport.trimOrNull(template),
+ "Certificate template cannot be null or empty");
}
/**
- * Sets the storage key suffix to add when resolving the CRL(s).
+ * Sets the Velocity template string for the CRL's storage key.
*
- * <p>Defaults to null, which skips attempted resolution of CRLs.</p>
+ * <p>Defaults to null, ignoring the CRL component.</p>
*
- * @param suffix suffix to add
+ * @param template template string
*/
- public void setCRLSuffix(@Nullable @NotEmpty final String suffix) {
+ public void setCRLTemplateString(@Nullable @NotEmpty final String template) {
checkSetterPreconditions();
- crlSuffix = StringSupport.trimOrNull(suffix);
+ crlTemplateString = StringSupport.trimOrNull(template);
}
/**
@@ -128,26 +142,47 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
privateKeyPassword = password != null ? password.toCharArray() : null;
}
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ privateKeyTemplate = Template.fromTemplate(getVelocityEngine(), privateKeyTemplateString,
+ StandardCharsets.UTF_8);
+ certificateTemplate = Template.fromTemplate(getVelocityEngine(), certificateTemplateString,
+ StandardCharsets.UTF_8);
+ final String local = crlTemplateString;
+ if (local != null) {
+ crlTemplate = Template.fromTemplate(getVelocityEngine(), local, StandardCharsets.UTF_8);
+ }
+ }
+
/** {@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 {
+ @Nonnull @NotLive @Unmodifiable protected Iterable<Credential> doResolve(@Nonnull VelocityContext velocityContext,
+ @Nullable final CriteriaSet criteria, @Nonnull final String storageContext) 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);
+ final String keyStorage = privateKeyTemplate.merge(velocityContext);
+ final String certStorage = certificateTemplate.merge(velocityContext);
+
+ log.debug("{}: Resolved storage keys for key ({}), certificate ({})", getId(), keyStorage, certStorage);
+
+ privateKeyData = getStorageService().read(storageContext, keyStorage);
+ certificateData = getStorageService().read(storageContext, certStorage);
+ if (crlTemplate != null) {
+ crlData = getStorageService().read(storageContext, crlTemplate.merge(velocityContext));
} else {
crlData = null;
}
+ } catch (final VelocityException e) {
+ throw new ResolverException("Error executing storage key template", e);
} catch (final IOException e) {
throw new ResolverException("I/O exception retrieving credential data", e);
}
@@ -157,6 +192,8 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
return CollectionSupport.emptyList();
}
+ log.debug("{}: Resolved data for key and/or certificate, constructing credential", getId());
+
// Populate a factory bean from the resolved material.
final BasicX509CredentialFactoryBean factory = new BasicX509CredentialFactoryBean();
@@ -179,6 +216,7 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
}
try {
+ factory.afterPropertiesSet();
final X509Credential credential = factory.getObject();
if (credential != null) {
log.debug("{}: Resolved X509Credential for caller", getId());
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list