[java-plugin-shibd] branch main updated: Initial commit of new code for CredentialResolver service.

Codeberg noreply at shibboleth.net
Thu Jul 23 15:57:13 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/a013482b395489b3fad291d84304a8c24afb957d

The following commit(s) were added to refs/heads/main by this push:
     new a013482  Initial commit of new code for CredentialResolver service.
a013482 is described below

commit a013482b395489b3fad291d84304a8c24afb957d
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Thu Jul 23 11:56:58 2026 -0400

    Initial commit of new code for CredentialResolver service.
---
 .../shibboleth/sp/AgentCredentialCriterion.java    |   4 +-
 ...dentialCriterion.java => AgentIDCriterion.java} |  18 +-
 ...lCriterion.java => ApplicationIDCriterion.java} |  18 +-
 .../AbstractOrderedCredentialResolver.java         | 190 +++++++++++++++++++++
 .../sp/credential/OrderedCredentialResolver.java   |  26 +++
 sp-server-impl/pom.xml                             |  11 ++
 .../credential/impl/CredentialResolverBridge.java  | 161 +++++++++++++++++
 .../credential/impl/StaticCredentialResolver.java  |  58 +++++++
 .../impl/StaticCriterionCredentialResolver.java    |  85 +++++++++
 .../impl/BasicHttpClientSecurityConfiguration.java | 107 ++++++++++++
 10 files changed, 658 insertions(+), 20 deletions(-)

diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/AgentCredentialCriterion.java b/sp-server-api/src/main/java/net/shibboleth/sp/AgentCredentialCriterion.java
index efe19f4..34a72eb 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/AgentCredentialCriterion.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/AgentCredentialCriterion.java
@@ -22,11 +22,11 @@ import net.shibboleth.shared.primitive.StringSupport;
 import net.shibboleth.shared.resolver.Criterion;
 
 /**
- * Resolver {@link Criterion} identifying an agent by means of a "credential" name.
+ * Resolver {@link Criterion} identifying an {@link Agent} by means of a "credential" name.
  */
 public class AgentCredentialCriterion implements Criterion {
     
-    /** Application ID. */
+    /** Agent credential ID/Username. */
     @Nonnull @NotEmpty final String credentialId;
 
     /**
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/AgentCredentialCriterion.java b/sp-server-api/src/main/java/net/shibboleth/sp/AgentIDCriterion.java
similarity index 65%
copy from sp-server-api/src/main/java/net/shibboleth/sp/AgentCredentialCriterion.java
copy to sp-server-api/src/main/java/net/shibboleth/sp/AgentIDCriterion.java
index efe19f4..35f6a86 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/AgentCredentialCriterion.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/AgentIDCriterion.java
@@ -22,29 +22,29 @@ import net.shibboleth.shared.primitive.StringSupport;
 import net.shibboleth.shared.resolver.Criterion;
 
 /**
- * Resolver {@link Criterion} identifying an agent by means of a "credential" name.
+ * Resolver {@link Criterion} identifying an {@link Agent} by means of its unique ID.
  */
-public class AgentCredentialCriterion implements Criterion {
+public class AgentIDCriterion implements Criterion {
     
-    /** Application ID. */
-    @Nonnull @NotEmpty final String credentialId;
+    /** Agent ID. */
+    @Nonnull @NotEmpty final String agentId;
 
     /**
      * Constructor.
      *
      * @param id agent ID
      */
-    public AgentCredentialCriterion(@Nonnull @NotEmpty final String id) {
-        credentialId = Constraint.isNotNull(StringSupport.trimOrNull(id), "Agent credential name cannot be null or empty");
+    public AgentIDCriterion(@Nonnull @NotEmpty final String id) {
+        agentId = Constraint.isNotNull(StringSupport.trimOrNull(id), "Agent ID cannot be null or empty");
     }
     
     /**
-     * Get the agent credential ID specified.
+     * Get the agent ID specified.
      * 
-     * @return agent credential ID
+     * @return agent ID
      */
     @Nonnull @NotEmpty public String getId() {
-        return credentialId;
+        return agentId;
     }
 
 }
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/AgentCredentialCriterion.java b/sp-server-api/src/main/java/net/shibboleth/sp/ApplicationIDCriterion.java
similarity index 65%
copy from sp-server-api/src/main/java/net/shibboleth/sp/AgentCredentialCriterion.java
copy to sp-server-api/src/main/java/net/shibboleth/sp/ApplicationIDCriterion.java
index efe19f4..d4dcc10 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/AgentCredentialCriterion.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/ApplicationIDCriterion.java
@@ -22,29 +22,29 @@ import net.shibboleth.shared.primitive.StringSupport;
 import net.shibboleth.shared.resolver.Criterion;
 
 /**
- * Resolver {@link Criterion} identifying an agent by means of a "credential" name.
+ * Resolver {@link Criterion} identifying an {@link Application} by means of its {@link Agent}-specific ID.
  */
-public class AgentCredentialCriterion implements Criterion {
+public class ApplicationIDCriterion implements Criterion {
     
     /** Application ID. */
-    @Nonnull @NotEmpty final String credentialId;
+    @Nonnull @NotEmpty final String applicationId;
 
     /**
      * Constructor.
      *
-     * @param id agent ID
+     * @param id application ID
      */
-    public AgentCredentialCriterion(@Nonnull @NotEmpty final String id) {
-        credentialId = Constraint.isNotNull(StringSupport.trimOrNull(id), "Agent credential name cannot be null or empty");
+    public ApplicationIDCriterion(@Nonnull @NotEmpty final String id) {
+        applicationId = Constraint.isNotNull(StringSupport.trimOrNull(id), "Application ID cannot be null or empty");
     }
     
     /**
-     * Get the agent credential ID specified.
+     * Get the application ID specified.
      * 
-     * @return agent credential ID
+     * @return application ID
      */
     @Nonnull @NotEmpty public String getId() {
-        return credentialId;
+        return applicationId;
     }
 
 }
\ No newline at end of file
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
new file mode 100644
index 0000000..bffe302
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractOrderedCredentialResolver.java
@@ -0,0 +1,190 @@
+/*
+ * 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.util.Collection;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.saml.criterion.ProtocolCriterion;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.slf4j.Logger;
+import org.springframework.core.Ordered;
+
+import com.google.common.collect.Iterables;
+
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+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;
+
+/**
+ * Simple base class for resolvers to supply an {@link Ordered} implementation if they don't
+ * need a dedicated base class.
+ * 
+ * <p>Additional "value-adds" include the component handling, the resolveSingle method, and
+ * built-in support for {@link UsageCriterion} and {@link ProtocolCriterion} filtering.</p>
+ */
+public abstract class AbstractOrderedCredentialResolver extends AbstractIdentifiableInitializableComponent
+        implements OrderedCredentialResolver {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractOrderedCredentialResolver.class); 
+    
+    /** Order. */
+    private int order;
+
+    /** Whether to apply usage filteing. */
+    private boolean filterByUsage;
+    
+    /** Protocol values supported by these credentials. */
+    private Set<String> supportedProtocols;
+    
+    /** {@inheritDoc} */
+    public int getOrder() {
+        return order;
+    }
+
+    /**
+     * Set component order.
+     * 
+     * @param i order value
+     */
+    public void setOrder(final int i) {
+        checkSetterPreconditions();
+        order = i;
+    }
+    
+    /**
+     * Sets whether to apply {@link UsageCriterion} if supplied to the results.
+     * 
+     * <p>Defaults to true.</p>
+     * 
+     * @param flag
+     */
+    public void setFilterByUsage(final boolean flag) {
+        checkSetterPreconditions();
+        filterByUsage = true;
+    }
+    
+    /**
+     * Sets the protocol values supported by this resolver.
+     * 
+     * <p>If set, any {@link ProtocolCriterion} will be enforced apriori before returning any credentials.</p>
+     * 
+     * @param protocols
+     */
+    public void setProtocols(@Nullable final Collection<String> protocols) {
+        checkSetterPreconditions();
+        if (protocols != null) {
+            supportedProtocols = CollectionSupport.copyToSet(StringSupport.normalizeStringCollection(protocols));
+        } else {
+            supportedProtocols = CollectionSupport.emptySet();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Nullable public Credential resolveSingle(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+        final Iterable<Credential> creds = resolve(criteriaSet);
+        if (creds.iterator().hasNext()) {
+            return creds.iterator().next();
+        }
+        return null;
+    }
+    
+    /** {@inheritDoc} */
+    @Nonnull public Iterable<Credential> resolve(@Nullable final CriteriaSet criteria) throws ResolverException {
+        checkComponentActive();
+        
+        // Check for protocol support.
+        final ProtocolCriterion protocolCriterion = criteria != null ? criteria.get(ProtocolCriterion.class) : null;
+        if (protocolCriterion != null && !supportedProtocols.contains(protocolCriterion.getProtocol())) {
+            log.debug("CredentialResolver {}: Unsupported protocol ({}), filtering out all credentials", getId(),
+                    protocolCriterion.getProtocol());
+            return CollectionSupport.emptyList();
+        }
+        
+        final Iterable<Credential> creds = doResolve(criteria);
+        
+        if (!filterByUsage) {
+            return creds;
+        }
+        
+        final UsageCriterion usage = criteria != null ? criteria.get(UsageCriterion.class) : null;
+        if (usage == null) {
+            return creds;
+        }
+        
+        log.debug("CredentialResolver {}: Filtering results by usage ({})", getId(), usage.getUsage());
+        
+        final UsageCriterionPredicate condition = new UsageCriterionPredicate(usage.getUsage());
+        return Iterables.filter(creds, condition::test);
+    }
+    
+    /**
+     * Subclasses implement this method to resolve the underlying objects.
+     * 
+     * @param criteria criteria for resolution
+     * 
+     * @return resolved credential iterator
+     * 
+     * @throws ResolverException if an error occurs
+     */
+    @Nonnull public abstract Iterable<Credential> doResolve(@Nullable final CriteriaSet criteria)
+            throws ResolverException;
+
+    /**
+     * This is more or less a duplicate of an OpenSAML implementation class to filter by usage.
+     */
+    public static class UsageCriterionPredicate implements Predicate<Credential> {
+
+        /** Usage to check for. */
+        @Nonnull UsageType usageType;
+        
+        /**
+         * Constructor.
+         *
+         * @param type usage type tp filter against
+         */
+        public UsageCriterionPredicate(@Nonnull final UsageType type) {
+            usageType = Constraint.isNotNull(type, "Usage type cannot be null");
+        }
+        
+        /** {@inheritDoc} */
+        public boolean test(@Nullable final Credential input) {
+            if (input == null) {
+                return false;
+            }
+            
+            final UsageType credUsage = input.getUsageType();
+            
+            if (credUsage == null || UsageType.UNSPECIFIED.equals(credUsage) || UsageType.UNSPECIFIED.equals(usageType)) {
+                return true;
+            }
+            
+            return usageType.equals(credUsage);
+        }
+        
+    }
+    
+}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/credential/OrderedCredentialResolver.java b/sp-server-api/src/main/java/net/shibboleth/sp/credential/OrderedCredentialResolver.java
new file mode 100644
index 0000000..55a8a69
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/credential/OrderedCredentialResolver.java
@@ -0,0 +1,26 @@
+/*
+ * 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 org.opensaml.security.credential.CredentialResolver;
+import org.springframework.core.Ordered;
+
+/**
+ * Marker interface for a {@link CredentialResolver} providing control over order
+ * during Spring auto-wiring.
+ */
+public interface OrderedCredentialResolver extends CredentialResolver, Ordered {
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/pom.xml b/sp-server-impl/pom.xml
index 49f4585..05cd9b2 100644
--- a/sp-server-impl/pom.xml
+++ b/sp-server-impl/pom.xml
@@ -91,6 +91,17 @@
             <artifactId>opensaml-profile-impl</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>${opensaml.groupId}</groupId>
+            <artifactId>opensaml-security-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!-- TODO: get various classes moved into -api -->
+        <dependency>
+            <groupId>${opensaml.groupId}</groupId>
+            <artifactId>opensaml-security-impl</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>${opensaml.groupId}</groupId>
             <artifactId>opensaml-storage-api</artifactId>
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/CredentialResolverBridge.java b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/CredentialResolverBridge.java
new file mode 100644
index 0000000..254a1fb
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/CredentialResolverBridge.java
@@ -0,0 +1,161 @@
+/*
+ * 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 javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.criterion.EntityIdCriterion;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.criterion.ProfileRequestContextCriterion;
+import org.opensaml.saml.criterion.ProtocolCriterion;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.slf4j.Logger;
+
+import jakarta.servlet.ServletRequest;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.service.ReloadableService;
+import net.shibboleth.shared.service.ServiceableComponent;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.AgentIDCriterion;
+import net.shibboleth.sp.Application;
+import net.shibboleth.sp.ApplicationIDCriterion;
+import net.shibboleth.sp.context.AgentRequestContext;
+
+/**
+ * Support object for bridging legacy OpenSAML interfaces to new credential resolver service.
+ * 
+ * <p>This is hackery along the lines of the original IdP transition to accepting the
+ * {@link ProfileRequestContext} into the getters aross much of the configuration by
+ * obtaining it "under the covers" from the {@link ServletRequest}, which is itself
+ * accessed implicitly via our epic filter hackery. It's hackery turtles!</p>
+ */
+public class CredentialResolverBridge implements CredentialResolver {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(CredentialResolverBridge.class);
+    
+    /** Credential resolver service. */
+    @Nonnull private final ReloadableService<CredentialResolver> credentialResolver;
+
+    /** Access to servlet request. */
+    @Nonnull private final NonnullSupplier<ServletRequest> servletRequestSupplier;
+    
+    /**
+     * Constructor.
+     * 
+     * @param resolver credential resolver service 
+     * @param requestSupplier supplier for {@link ServletRequest}
+     */
+    public CredentialResolverBridge(
+            @Nonnull @ParameterName(name="resolver") final ReloadableService<CredentialResolver> resolver,
+            @Nonnull @ParameterName(name="requestSupplier") final NonnullSupplier<ServletRequest> requestSupplier) {
+        credentialResolver = Constraint.isNotNull(resolver, "CredentialResolver cannot be null");
+        servletRequestSupplier = Constraint.isNotNull(requestSupplier, "ServletRequest supplier cannot be null");
+    }
+    
+    /**
+     * {@inheritDoc}
+     * 
+     * <p>In addition to the input, additional criteria will be added based on the existance of an active
+     * {@link ProfileRequestContext} and any state located via that object such as the active Agent, Application,
+     * RelyingParty identity, etc.</p>
+     * 
+     * <p>Typically the caller is expected to supply {@link UsageCriterion} and {@link ProtocolCriterion} if desired
+     * to limit what sorts of credentials will be resolved based on the caller's use case, and of course may
+     * supply others.</p>
+     */
+    @Nonnull public Iterable<Credential> resolve(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+        try (final ServiceableComponent<CredentialResolver> component = credentialResolver.getServiceableComponent()) {
+            return component.getComponent().resolve(buildCriteriaSet(criteriaSet));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Nullable public Credential resolveSingle(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+        final Iterable<Credential> creds = resolve(criteriaSet);
+        if (creds.iterator().hasNext()) {
+            return creds.iterator().next();
+        }
+        return null;
+    }
+    
+    /**
+     * Add or construct final {@link CriteriaSet} to supply to resolver.
+     * 
+     * @param criteria existing criteria to add to, if any
+     * 
+     * @return the final set, if any
+     */
+    @Nullable private CriteriaSet buildCriteriaSet(@Nullable final CriteriaSet criteria) {
+        
+        final ProfileRequestContext prc = getProfileRequestContext();
+        if (prc == null) {
+            log.debug("CredentialResolverBridge found no ProfileRequestContext to process");
+            return criteria;
+        }
+        
+        final CriteriaSet finalCriteria = criteria != null ? criteria : new CriteriaSet();
+        finalCriteria.add(new ProfileRequestContextCriterion(prc));
+        
+        // Add Agent/Application info.
+        final AgentRequestContext arc = prc.getSubcontext(AgentRequestContext.class);
+        if (arc != null) {
+            final Agent agent = arc.getAgent();
+            if (agent != null) {
+                final String id = agent.getId();
+                if (id != null) {
+                    finalCriteria.add(new AgentIDCriterion(id));
+                }
+            }
+            final Application app = arc.getApplication();
+            if (app != null) {
+                finalCriteria.add(new ApplicationIDCriterion(app.getApplicationId()));
+            }
+        }
+        
+        final RelyingPartyContext rpc = prc.getSubcontext(RelyingPartyContext.class);
+        if (rpc != null) {
+            final String rpid = rpc.getRelyingPartyId();
+            if (rpid != null) {
+                finalCriteria.add(new EntityIdCriterion(rpid));
+            }
+        }
+        
+        return finalCriteria;
+    }
+    
+    /**
+     * Get the current {@link ProfileRequestContext}.
+     * 
+     * @return current profile request context or null
+     */
+    @Nullable private ProfileRequestContext getProfileRequestContext() {
+        if (servletRequestSupplier.get().getAttribute(ProfileRequestContext.BINDING_KEY)
+                instanceof ProfileRequestContext prc) {
+            return prc;
+        }
+        return null;
+    }
+    
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..95643af
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticCredentialResolver.java
@@ -0,0 +1,58 @@
+/*
+ * 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.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+
+import net.shibboleth.shared.annotation.ParameterName;
+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.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.sp.credential.AbstractOrderedCredentialResolver;
+
+/**
+ * A static resolver that subclasses the functionality needed for SP usage.
+ */
+public class StaticCredentialResolver extends AbstractOrderedCredentialResolver {
+
+    /** List of credentials held by this resolver. */
+    @Nonnull private final List<Credential> creds;
+    
+    /**
+     * Constructor.
+     *
+     * @param credentials static credentials
+     */
+    public StaticCredentialResolver(@Nonnull @ParameterName(name="credentials") final List<Credential> credentials) {
+        Constraint.isNotNull(credentials, "Input credentials list cannot be null");
+        
+        creds = CollectionSupport.copyToList(credentials);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull @NotLive @Unmodifiable public Iterable<Credential> doResolve(@Nullable final CriteriaSet criteria) throws ResolverException {
+        return creds;
+    }
+    
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticCriterionCredentialResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticCriterionCredentialResolver.java
new file mode 100644
index 0000000..e3a8a35
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/StaticCriterionCredentialResolver.java
@@ -0,0 +1,85 @@
+/*
+ * 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.util.Collection;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
+import org.opensaml.xmlsec.keyinfo.KeyInfoCriterion;
+
+import net.shibboleth.shared.annotation.ParameterName;
+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.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.Criterion;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * A resolver that adds statically-defined {@link Criterion} objects to the {@link CriteriaSet}
+ * fed into an underlying resolver.
+ * 
+ * <p>It exports the additional {@link KeyInfoCredentialResolver} interface so it can be used in that
+ * context, but does not itself process a {@link KeyInfoCriterion}.</p>
+ */
+public class StaticCriterionCredentialResolver implements KeyInfoCredentialResolver {
+
+    /** List of credentials held by this resolver. */
+    @Nonnull private final CredentialResolver credentialResolver;
+    
+    /** Static criteria to add. */
+    @Nonnull private final Collection<Criterion> staticCriteria;
+    
+    /**
+     * Constructor.
+     *
+     * @param resolver underlying resolver
+     * @param criteria static criteria to add to all resolutions
+     */
+    public StaticCriterionCredentialResolver(@Nonnull @ParameterName(name="resolver") final CredentialResolver resolver,
+            @Nullable @ParameterName(name="criteria") final Collection<Criterion> criteria) {
+        credentialResolver = Constraint.isNotNull(resolver, "CredentialResolver cannot be null");
+        if (criteria != null) {
+            staticCriteria = CollectionSupport.copyToSet(criteria);
+        } else {
+            staticCriteria = CollectionSupport.emptySet();
+        }
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull @NotLive @Unmodifiable public Iterable<Credential> resolve(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+        final CriteriaSet finalCriteria = criteriaSet != null ? criteriaSet : new CriteriaSet();
+        finalCriteria.addAll(staticCriteria);
+        return credentialResolver.resolve(finalCriteria);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable public Credential resolveSingle(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+        final Iterable<Credential> creds = resolve(criteriaSet);
+        if (creds.iterator().hasNext()) {
+            return creds.iterator().next();
+        }
+        return null;
+    }
+    
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/security/config/impl/BasicHttpClientSecurityConfiguration.java b/sp-server-impl/src/main/java/net/shibboleth/sp/security/config/impl/BasicHttpClientSecurityConfiguration.java
new file mode 100644
index 0000000..7fec068
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/security/config/impl/BasicHttpClientSecurityConfiguration.java
@@ -0,0 +1,107 @@
+/*
+ * 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.security.config.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.saml.criterion.ProtocolCriterion;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.security.x509.X509Credential;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.resolver.ClassCriterion;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * Subclass of OpenSAML version to provide support for CredentialResolver service
+ * through a bridging class.
+ */
+public class BasicHttpClientSecurityConfiguration
+        extends org.opensaml.security.httpclient.impl.BasicHttpClientSecurityConfiguration {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(BasicHttpClientSecurityConfiguration.class);
+    
+    /** Bridge to resolve credentials. */
+    @Nonnull private final CredentialResolver credentialResolver; 
+    
+    /** Protocol designation. */
+    @Nullable private String protocol;
+    
+    /**
+     * Constructor.
+     *
+     * @param resolver a credential resolver service to use
+     */
+    public BasicHttpClientSecurityConfiguration(
+            @Nonnull @ParameterName(name="resolver") final CredentialResolver resolver) {
+        credentialResolver = Constraint.isNotNull(resolver, "CredentialResolver cannot be null");
+    }
+    
+    /**
+     * Sets the protocol designation for credential resolution, if any.
+     * 
+     * @param proto protocol support value
+     */
+    public void setProtocol(@Nullable final String proto) {
+        protocol = StringSupport.trimOrNull(protocol);
+    }
+    
+    /**
+     * Get the optional client TLS credential.
+     * 
+     * @return the client TLS credential, or null
+     */
+    @Nullable public X509Credential getClientTLSCredential() {
+        final X509Credential hardwired = super.getClientTLSCredential();
+        if (hardwired != null) {
+            return hardwired;
+        }
+        
+        // Resolve if possible using:
+        //  * UsageType = SIGNING
+        //  * Class = X509Credential
+        //  * Protocol = if set
+        
+        final CriteriaSet criteria =
+                new CriteriaSet(new UsageCriterion(UsageType.SIGNING), new ClassCriterion<>(X509Credential.class));
+        if (protocol != null) {
+            criteria.add(new ProtocolCriterion(protocol));
+        }
+        
+        try {
+            final Credential cred = credentialResolver.resolveSingle(criteria);
+            if (cred instanceof X509Credential x509) {
+                return x509;
+            }
+            
+            log.warn("Non-X509Credential returned, ignoring it");
+        } catch (final ResolverException e) {
+            log.error("Exception resolving X509Credential for client TLS", e);
+        }
+        
+        return null;
+    }
+
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list