[java-opensaml] 01/02: JSATTR-6: SAML AttributeQuery DataConnector

Brent Putman putmanb at georgetown.edu
Thu May 29 23:47:57 UTC 2025


This is an automated email from the git hooks/post-receive script.

putmanb pushed a commit to branch main
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=34cf236b26d27e0ae35f6889a367c7579ccdaf5e

commit 34cf236b26d27e0ae35f6889a367c7579ccdaf5e
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Mon Mar 10 14:11:54 2025 -0400

    JSATTR-6: SAML AttributeQuery DataConnector
    
    Add variant of DefaultAssertionValidationContextBuilder and related
    classes which is based on InOutOperationContext rather than
    ProfileRequestContext.
    
    Add new methods to a couple of support classes.
---
 .../saml/common/binding/SAMLBindingSupport.java    |   24 +
 .../messaging/AssertionValidationInput.java        |   80 ++
 ...ertionValidationNetworkInformationSupplier.java |   51 +
 .../messaging/BasicNetworkInformationSupplier.java |   83 ++
 ...tpServletRequestNetworkInformationSupplier.java |   74 ++
 .../saml2/assertion/messaging/package-info.java    |   21 +
 .../saml/saml2/profile/SAML2ObjectSupport.java     |   60 +
 .../DefaultAssertionValidationContextBuilder.java  | 1239 ++++++++++++++++++++
 .../assertion/messaging/impl/package-info.java     |   22 +
 .../saml/saml2/profile/SAML2ObjectSupportTest.java |  143 +++
 10 files changed, 1797 insertions(+)

diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/common/binding/SAMLBindingSupport.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/common/binding/SAMLBindingSupport.java
index 6cf020a08..dae771756 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/common/binding/SAMLBindingSupport.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/common/binding/SAMLBindingSupport.java
@@ -366,6 +366,30 @@ public final class SAMLBindingSupport {
         return request.getRequestURL().toString();
     }
     
+    /**
+     * Extract the transport endpoint URI at which this message was received.
+     * 
+     * @param messageContext current message context
+     * @return string representing the transport endpoint URI at which the current message was received
+     * @throws MessageException thrown if the endpoint can not be looked up from the message
+     *                              context and converted to a string representation
+     *                              
+     * @since 5.2.0
+     */
+    @Nullable public static String getActualReceiverEndpointURI(@Nonnull final MessageContext messageContext)
+            throws MessageException {
+
+        final SAMLMessageReceivedEndpointContext receivedEnpointContext =
+                messageContext.getSubcontext(SAMLMessageReceivedEndpointContext.class);
+        if (receivedEnpointContext != null) {
+            final String url = receivedEnpointContext.getRequestURL();
+            if (url != null) {
+                return url;
+            }
+        }
+        return null;
+    }
+    
     /**
      * Convert a 2-byte artifact endpoint index byte[] as typically used by SAML 2 artifact types to an integer,
      * appropriate for use with {@link org.opensaml.saml.saml2.metadata.IndexedEndpoint} impls.
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/AssertionValidationInput.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/AssertionValidationInput.java
new file mode 100644
index 000000000..02e1a3387
--- /dev/null
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/AssertionValidationInput.java
@@ -0,0 +1,80 @@
+/*
+ * 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 org.opensaml.saml.saml2.assertion.messaging;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.saml.saml2.core.Assertion;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Class which holds data relevant to validating a SAML 2.0 Assertion.
+ */
+public class AssertionValidationInput {
+    
+    /** The profile request context input. */
+    @Nonnull private InOutOperationContext operationContext;
+    
+    /** The HTTP request input. */
+    @Nonnull private AssertionValidationNetworkInformationSupplier networkInformationSupplier;
+    
+    /** The Assertion being evaluated. */
+    @Nonnull private Assertion assertion;
+
+    /**
+     * Constructor.
+     * @param samlAssertion the assertion being evaluated
+     * @param context the profile request context being evaluated
+     * @param networkInformation the supplier of network information
+     */
+    public AssertionValidationInput(@Nonnull final Assertion samlAssertion,
+            @Nonnull final InOutOperationContext context,
+            @Nonnull final AssertionValidationNetworkInformationSupplier networkInformation) {
+        operationContext = Constraint.isNotNull(context, "InOutOperationContext may not be null");
+        networkInformationSupplier = Constraint.isNotNull(networkInformation, "HttpServletRequest may not be null");
+        assertion = Constraint.isNotNull(samlAssertion, "Assertion may not be null");
+    }
+
+    /**
+     * Get the {@link InOutOperationContext} input.
+     * 
+     * @return the message context input
+     */
+    @Nonnull public InOutOperationContext getOperationContext() {
+        return operationContext;
+    }
+
+    /**
+     * Get the {@link HttpServletRequest} input.
+     * 
+     * @return the HTTP servlet request input
+     */
+    @Nonnull public AssertionValidationNetworkInformationSupplier getNetworkInformationSupplier() {
+        return networkInformationSupplier;
+    }
+
+    /**
+     * Get the {@link Assertion} being evaluated.
+     * 
+     * @return the Assertion being validated
+     */
+    @Nonnull public Assertion getAssertion() {
+        return assertion;
+    }
+
+}
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/AssertionValidationNetworkInformationSupplier.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/AssertionValidationNetworkInformationSupplier.java
new file mode 100644
index 000000000..71227d3ac
--- /dev/null
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/AssertionValidationNetworkInformationSupplier.java
@@ -0,0 +1,51 @@
+/*
+ * 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 org.opensaml.saml.saml2.assertion.messaging;
+
+import java.security.cert.X509Certificate;
+
+import javax.annotation.Nullable;
+
+/**
+ * Interface for a component that supplies network-related information for assertion validation.
+ */
+public interface AssertionValidationNetworkInformationSupplier {
+    
+    /**
+     * The X.509 certificate presented by the attesting party, may be null.
+     * 
+     * @return the attesting party's certificate, or null
+     */
+    @Nullable X509Certificate getAttesterCertificate();
+    
+    /**
+     * The attesting party's IP address, may be null.
+     * 
+     * <p>
+     * For IPv6 addresses this should be in "sanitized" form, without enclosing square brackets.
+     * </p>
+     * 
+     * @return the attesting party's IP address, or null if not known
+     */
+    @Nullable String getAttesterIPAddress();
+    
+    /**
+     * The endpoint URI at which the assertion being validated was received, may be null.
+     * 
+     * @return the endpoint URI, or null if not applicable
+     */
+    @Nullable String getReceiverEndpointURI();
+
+}
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/BasicNetworkInformationSupplier.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/BasicNetworkInformationSupplier.java
new file mode 100644
index 000000000..2df1ba3eb
--- /dev/null
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/BasicNetworkInformationSupplier.java
@@ -0,0 +1,83 @@
+/*
+ * 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 org.opensaml.saml.saml2.assertion.messaging;
+
+import java.security.cert.X509Certificate;
+
+import javax.annotation.Nullable;
+
+/**
+ * Basic property-based implementation of {@link AssertionValidationNetworkInformationSupplier}.
+ */
+public class BasicNetworkInformationSupplier implements AssertionValidationNetworkInformationSupplier {
+    
+    /** The attesting party's certificate. */
+    @Nullable X509Certificate attesterCertificate;
+    
+    /** The attesting party's IP address. */
+    @Nullable String attesterIPAddress;
+    
+    /** The endpoint URI at which the assertion for validation was received. */
+    @Nullable String receiverEndpointURI;
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public X509Certificate getAttesterCertificate() {
+        return attesterCertificate;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public String getAttesterIPAddress() {
+        return attesterIPAddress;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public String getReceiverEndpointURI() {
+        return receiverEndpointURI;
+    }
+
+    /**
+     * Set the attesting party's certificate.
+     * 
+     * @param certificate the attesting party's certificate
+     */
+    public void setAttesterCertificate(@Nullable final X509Certificate certificate) {
+        attesterCertificate = certificate;
+    }
+
+    /**
+     * Set the attesting party's IP address.
+     * 
+     * @param address the attesting party's IP address
+     */
+    public void setAttesterIPAddress(@Nullable final String address) {
+        attesterIPAddress = address;
+    }
+
+    /**
+     * Set the endpoint URI at which the assertion being validated was received.
+     * 
+     * @param endpointURI the endpoint URI
+     */
+    public void setReceiverEndpointURI(@Nullable final String endpointURI) {
+        receiverEndpointURI = endpointURI;
+    }
+
+}
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/HttpServletRequestNetworkInformationSupplier.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/HttpServletRequestNetworkInformationSupplier.java
new file mode 100644
index 000000000..72f578dd6
--- /dev/null
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/HttpServletRequestNetworkInformationSupplier.java
@@ -0,0 +1,74 @@
+/*
+ * 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 org.opensaml.saml.saml2.assertion.messaging;
+
+import java.security.cert.X509Certificate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.messaging.ServletRequestX509CredentialAdapter;
+import org.opensaml.security.x509.X509Credential;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.servlet.HttpServletSupport;
+
+/**
+ * Implementation of {@link AssertionValidationNetworkInformationSupplier} which wraps an 
+ * instance of {@link HttpServletRequest}.
+ */
+public class HttpServletRequestNetworkInformationSupplier implements AssertionValidationNetworkInformationSupplier {
+    
+    /** The wrapped servlet request. */
+    @Nonnull private HttpServletRequest servletRequest;
+
+    /**
+     * Constructor.
+     *
+     * @param request the servlet request instanc3
+     */
+    public HttpServletRequestNetworkInformationSupplier(@Nonnull final HttpServletRequest request) {
+        servletRequest = Constraint.isNotNull(request, "HttpServletRequest was null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public X509Certificate getAttesterCertificate() {
+        try {
+            final X509Credential credential = new ServletRequestX509CredentialAdapter(servletRequest);
+            return credential.getEntityCertificate();
+        } catch (final SecurityException e) {
+            return null;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public String getAttesterIPAddress() {
+        return HttpServletSupport.getRemoteAddr(servletRequest);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public String getReceiverEndpointURI() {
+        return servletRequest.getRequestURL().toString();
+    }
+
+}
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/package-info.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/package-info.java
new file mode 100644
index 000000000..36426db36
--- /dev/null
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/messaging/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Interfaces and API classes related to networking and messaging APIs that support validating SAML 2 Assertions.
+ */
+ at NonnullElements
+package org.opensaml.saml.saml2.assertion.messaging;
+
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/profile/SAML2ObjectSupport.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/profile/SAML2ObjectSupport.java
index 4b09d4dfa..bedda0014 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/profile/SAML2ObjectSupport.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/profile/SAML2ObjectSupport.java
@@ -19,13 +19,23 @@ import java.util.Objects;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import org.opensaml.saml.saml2.core.BaseID;
+import org.opensaml.saml.saml2.core.EncryptedID;
 import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.Subject;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
  * A helper class for working with SAMLObjects.
  */
 public final class SAML2ObjectSupport {
     
+    /** Logger. */
+    @Nonnull private static final Logger LOG = LoggerFactory.getLogger(SAML2ObjectSupport.class);
+    
     /** Constructor. */
     private SAML2ObjectSupport() {
         
@@ -107,5 +117,55 @@ public final class SAML2ObjectSupport {
         }
         return Objects.equals(name1qual, name2qual);
     }
+    
+    /**
+     * Match a target {@link Subject} against a control instance according to the requirements specified
+     * in SAML Core 3.3.4.
+     * 
+     * <p>
+     * Any {@link EncryptedID} instances which were originally present must have already been decrypted
+     * and stored in-place on the Subject. {@link BaseID} is currently unsupported. Presence of either
+     * in either target or control subject will throw {@link IllegalArgumentException}.
+     * </p>
+     * 
+     * @param target the target subject to evaluate
+     * @param control the control subject against which to evaluate the target
+     * 
+     * @return true if target matches the control, otherwise false
+     * 
+     * @throws IllegalArgumentException if EncryptedID or BaseID is present in either Subject instance
+     */
+    public static boolean matchSubject(@Nonnull final Subject target, @Nonnull final Subject control) {
+        Constraint.isNotNull(target, "Target Subject was null");
+        Constraint.isNotNull(control, "Control Subject was null");
+        
+        //TODO implement SubjectConfirmation support. Need registry of method URI -> SC matchers
+        if (!target.getSubjectConfirmations().isEmpty()) {
+            LOG.warn("Target Subject contains SubjectConfirmation, currently not supported and eval is skipped");
+        }
+        
+        if (target.getEncryptedID() != null || control.getEncryptedID() != null) {
+            throw new IllegalArgumentException("Saw EncryptedID in Subject, matching not supported");
+        }
+        
+        if (target.getBaseID() != null || control.getBaseID() != null) {
+            throw new IllegalArgumentException("Saw BaseID in Subject, matching not supported");
+        }
+        
+        final NameID targetNameID = target.getNameID();
+        final NameID controlNameID = control.getNameID();
+        
+        if (targetNameID == null && controlNameID == null) {
+            LOG.debug("Both target and control NameIDs are null, trivially match");
+            return true;
+        }
+        if (targetNameID == null || controlNameID == null) {
+            LOG.debug("One NameID is null ({}), the other is not, trivially do not match",
+                    targetNameID == null ? "target" : "control");
+            return false;
+        }
+
+        return areNameIDsEquivalent(targetNameID, controlNameID);
+    }
 
 }
\ No newline at end of file
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/messaging/impl/DefaultAssertionValidationContextBuilder.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/messaging/impl/DefaultAssertionValidationContextBuilder.java
new file mode 100644
index 000000000..a2a305a4b
--- /dev/null
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/messaging/impl/DefaultAssertionValidationContextBuilder.java
@@ -0,0 +1,1239 @@
+/*
+ * 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 org.opensaml.saml.saml2.assertion.messaging.impl;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.xml.namespace.QName;
+
+import org.opensaml.core.criterion.EntityIdCriterion;
+import org.opensaml.messaging.MessageException;
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.messaging.context.navigate.MessageContextLookup;
+import org.opensaml.messaging.context.navigate.MessageContextLookup.Direction;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.assertion.ValidationContext;
+import org.opensaml.saml.common.binding.SAMLBindingSupport;
+import org.opensaml.saml.common.messaging.context.SAMLMessageInfoContext;
+import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
+import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
+import org.opensaml.saml.common.messaging.context.SAMLProtocolContext;
+import org.opensaml.saml.common.messaging.context.SAMLSelfEntityContext;
+import org.opensaml.saml.common.messaging.context.navigate.SAMLEntityIDFunction;
+import org.opensaml.saml.common.messaging.context.navigate.SAMLMessageInfoContextIDFunction;
+import org.opensaml.saml.criterion.EntityRoleCriterion;
+import org.opensaml.saml.criterion.ProtocolCriterion;
+import org.opensaml.saml.criterion.RoleDescriptorCriterion;
+import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
+import org.opensaml.saml.saml2.assertion.messaging.AssertionValidationInput;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.NameIDType;
+import org.opensaml.saml.saml2.metadata.RoleDescriptor;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.xmlsec.SignatureValidationParameters;
+import org.opensaml.xmlsec.context.SecurityParametersContext;
+import org.opensaml.xmlsec.signature.support.SignatureValidationParametersCriterion;
+import org.slf4j.Logger;
+
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.LazySet;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ *  Function which implements default behavior for building an instance of {@link ValidationContext}
+ *  from an instance of {@link AssertionValidationInput}.
+ */
+public class DefaultAssertionValidationContextBuilder 
+        implements Function<AssertionValidationInput, ValidationContext> {
+    
+    /** Logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultAssertionValidationContextBuilder.class);
+    
+    /** A function for resolving the clock skew to apply. */
+    @Nullable private Function<InOutOperationContext, Duration> clockSkew;
+    
+    /** A function for resolving the lifetime to apply. */
+    @Nullable private Function<InOutOperationContext, Duration> lifetime;
+    
+    /** A function for resolving the signature validation CriteriaSet for a particular function. */
+    @Nullable private Function<Pair<InOutOperationContext, Assertion>, CriteriaSet> signatureCriteriaSetFunction;
+    
+    /** Predicate for determining whether an Assertion signature is required. */
+    @Nonnull private Predicate<InOutOperationContext> signatureRequired;
+    
+    /** Predicate for determining whether an Assertion's network address(es) should be checked. */
+    @Nonnull private Predicate<InOutOperationContext> checkAddress;
+    
+    /** Function for determining the max allowed time since authentication. */
+    @Nullable private Function<InOutOperationContext, Duration> maximumTimeSinceAuthn;
+
+    /** Predicate for determining whether to include the self entityID as a valid Recipient. */
+    @Nonnull private Predicate<InOutOperationContext> includeSelfEntityIDAsRecipient;
+    
+    /** Function for determining additional valid audience values. */
+    @Nullable private Function<InOutOperationContext, Set<String>> additionalAudiences;
+    
+    /** Function for determining additional valid Issuer values. */
+    @Nonnull private Function<InOutOperationContext, Set<String>> validIssuers;
+    
+    /** Predicate for determining whether to require issuer be of the {@link NameIDType#ENTITY} format. */
+    @Nonnull private Predicate<InOutOperationContext> requireEntityIssuer;
+    
+    /** Function for determining the valid InResponseTo value. */
+    @Nullable private Function<InOutOperationContext, String> inResponseTo;
+    
+    /** Predicate for determining whether an Assertion SubjectConfirmationData InResponseTo is ignored. */
+    @Nonnull private Predicate<InOutOperationContext> inResponseToIgnored;
+    
+    /** Predicate for determining whether an Assertion SubjectConfirmationData InResponseTo is required. */
+    @Nonnull private Predicate<InOutOperationContext> inResponseToRequired;
+    
+    /** Predicate for determining whether an Assertion SubjectConfirmationData Recipient is required. */
+    @Nonnull private Predicate<InOutOperationContext> recipientRequired;
+    
+    /** Predicate for determining whether an Assertion SubjectConfirmationData NotBefore is required. */
+    @Nonnull private Predicate<InOutOperationContext> notBeforeRequired;
+    
+    /** Predicate for determining whether an Assertion SubjectConfirmationData NotOnOrAfter is required. */
+    @Nonnull private Predicate<InOutOperationContext> notOnOrAfterRequired;
+    
+    /** Predicate for determining whether an Assertion SubjectConfirmationData Address is required. */
+    @Nonnull private Predicate<InOutOperationContext> addressRequired;
+    
+    /** The set of required Conditions. */
+    @Nonnull private Set<QName> requiredConditions;
+
+    /** Resolver for security parameters context. */
+    private Function<InOutOperationContext, SecurityParametersContext> securityParametersLookupStrategy;
+
+    /**
+     * Constructor.
+     */
+    public DefaultAssertionValidationContextBuilder() {
+        signatureRequired = PredicateSupport.alwaysTrue();
+        includeSelfEntityIDAsRecipient = PredicateSupport.alwaysFalse();
+        checkAddress = PredicateSupport.alwaysTrue();
+        inResponseTo = new DefaultValidInResponseToLookupFunction();
+        inResponseToIgnored = PredicateSupport.alwaysFalse();
+        inResponseToRequired = PredicateSupport.alwaysFalse();
+        recipientRequired = PredicateSupport.alwaysFalse();
+        notOnOrAfterRequired = PredicateSupport.alwaysFalse();
+        notBeforeRequired = PredicateSupport.alwaysFalse();
+        addressRequired = PredicateSupport.alwaysFalse();
+        requiredConditions = CollectionSupport.emptySet();
+        validIssuers = new DefaultValidIssuersLookupFunction();
+        requireEntityIssuer = PredicateSupport.alwaysFalse();
+
+        securityParametersLookupStrategy = new ChildContextLookup<>(SecurityParametersContext.class)
+                .compose(new ContextDataLookupFunction<InOutOperationContext, MessageContext>() {
+                    public MessageContext apply(@Nullable InOutOperationContext t) {
+                        return t != null ? t.getInboundMessageContext() : null;
+                    }
+                });
+    }
+    
+    /**
+     * Get the strategy by which to resolve the clock skew.
+     * 
+     * @return lookup strategy
+     * 
+     * @since 4.1.0
+     */
+    @Nullable public Function<InOutOperationContext, Duration> getClockSkew() {
+        return clockSkew;
+    }
+
+    /**
+     * Set the clock skew.
+     * 
+     * @param skew clock skew
+     * 
+     * @since 4.1.0
+     */
+    public void setClockSkew(@Nullable final Duration skew) {
+        clockSkew = FunctionSupport.constant(skew);
+    }
+
+    /**
+     * Set the strategy by which to resolve the clock skew.
+     * 
+     * @param strategy lookup strategy
+     * 
+     * @since 4.1.0
+     */
+    public void setClockSkewLookupStrategy(@Nullable final Function<InOutOperationContext, Duration> strategy) {
+        clockSkew = strategy;
+    }
+
+    /**
+     * Get the strategy by which to resolve the lifetime.
+     * 
+     * @return lookup strategy
+     * 
+     * @since 4.2.0
+     */
+    @Nullable public Function<InOutOperationContext, Duration> getLifetime() {
+        return lifetime;
+    }
+
+    /**
+     * Set the lifetime.
+     * 
+     * @param duration lifetime
+     * 
+     * @since 4.2.0
+     */
+    public void setLifetime(@Nullable final Duration duration) {
+        lifetime = FunctionSupport.constant(duration);
+    }
+
+    /**
+     * Set the strategy by which to resolve the lifetime.
+     * 
+     * @param strategy lookup strategy
+     * 
+     * @since 4.2.0
+     */
+    public void setLifetimeLookupStrategy(@Nullable final Function<InOutOperationContext, Duration> strategy) {
+        lifetime = strategy;
+    }
+
+    /**
+     * Get the strategy by which to resolve a {@link SecurityParametersContext}.
+     *
+     * @return the lookup strategy
+     */
+    @Nonnull public Function<InOutOperationContext, SecurityParametersContext> getSecurityParametersLookupStrategy() {
+        return securityParametersLookupStrategy;
+    }
+
+    /**
+     * Set the strategy by which to resolve a {@link SecurityParametersContext}.
+     *
+     * @param strategy the strategy function
+     */
+    public void setSecurityParametersLookupStrategy(
+            @Nonnull final Function<InOutOperationContext, SecurityParametersContext> strategy) {
+        securityParametersLookupStrategy =
+                Constraint.isNotNull(strategy, "SecurityParametersContext lookup strategy was null") ;
+    }
+
+    /**
+     * Get the set of required Conditions.
+     * 
+     * @return the required conditions, may be null
+     */
+    @Nonnull public Set<QName> getRequiredConditions() {
+        return requiredConditions;
+    }
+
+    /**
+     * Set the set of required Conditions.
+     * 
+     * @param conditions the required conditions
+     */
+    public void setRequiredConditions(@Nullable final Set<QName> conditions) {
+        if (conditions != null) {
+            requiredConditions = conditions.stream()
+                    .filter(Objects::nonNull)
+                    .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableSet())).get();
+        } else {
+            requiredConditions = CollectionSupport.emptySet();
+        }
+    }
+
+    /**
+     * Get the predicate which determines whether to include the self entityID as a valid Recipient.
+     * 
+     * <p>
+     * Defaults to an always false predicate;
+     * </p>
+     * 
+     * @return the predicate
+     */
+    @Nonnull public Predicate<InOutOperationContext> getIncludeSelfEntityIDAsRecipient() {
+        return includeSelfEntityIDAsRecipient;
+    }
+
+    /**
+     * Set the predicate which determines whether to include the self entityID as a valid Recipient.
+     * 
+     * <p>
+     * Defaults to an always false predicate.
+     * </p>
+     * 
+     * @param predicate the predicate, must be non-null
+     */
+    public void setIncludeSelfEntityIDAsRecipient(@Nonnull final Predicate<InOutOperationContext> predicate) {
+        includeSelfEntityIDAsRecipient = Constraint.isNotNull(predicate, "Signature required predicate was null");
+    }
+    
+    /**
+     * Get the predicate which determines whether an Assertion signature is required.
+     * 
+     * <p>
+     * Defaults to an always true predicate;
+     * </p>
+     * 
+     * @return the predicate
+     */
+    @Nonnull public Predicate<InOutOperationContext> getSignatureRequired() {
+        return signatureRequired;
+    }
+
+    /**
+     * Set the predicate which determines whether an Assertion signature is required.
+     * 
+     * <p>
+     * Defaults to an always true predicate.
+     * </p>
+     * 
+     * @param predicate the predicate, must be non-null
+     */
+    public void setSignatureRequired(@Nonnull final Predicate<InOutOperationContext> predicate) {
+        signatureRequired = Constraint.isNotNull(predicate, "Signature required predicate was null");
+    }
+
+    /**
+     * Set the function for determining the valid InResponseTo.
+     *
+     * <p>
+     * Defaults to null.
+     * </p>
+     *
+     * @param function the function, may be null
+     */
+    public void setInResponseTo(final @Nullable Function<InOutOperationContext,String> function) {
+        inResponseTo = function;
+    }
+    
+    /**
+     * Get the function for determining the valid InResponseTo.
+     *
+     * <p>
+     * Defaults to null.
+     * </p>
+     *
+     * @return the function
+     */
+    @Nullable public Function<InOutOperationContext,String> getInResponseTo() {
+        return inResponseTo;
+    }
+    
+    /**
+     * Get the predicate which determines whether an Assertion SubjectConfirmationData InResponseTo is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate;
+     * </p>
+     * 
+     * @return the predicate
+     */
+    @Nonnull public Predicate<InOutOperationContext> getInResponseToRequired() {
+        return inResponseToRequired;
+    }
+
+    /**
+     * Set the predicate which determines whether an Assertion SubjectConfirmationData InResponseTo is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate.
+     * </p>
+     * 
+     * @param predicate the predicate, must be non-null
+     */
+    public void setInResponseToRequired(@Nonnull final Predicate<InOutOperationContext> predicate) {
+        inResponseToRequired = Constraint.isNotNull(predicate, "InResponseTo required predicate was null");
+    }
+    
+    /**
+     * Get the predicate which determines whether an Assertion SubjectConfirmationData InResponseTo is ignored.
+     * 
+     * <p>
+     * Defaults to an always false predicate;
+     * </p>
+     * 
+     * @return the predicate
+     * 
+     * @since 5.2.0
+     */
+    @Nonnull public Predicate<InOutOperationContext> getInResponseToIgnored() {
+        return inResponseToIgnored;
+    }
+
+    /**
+     * Set the predicate which determines whether an Assertion SubjectConfirmationData InResponseTo is ignored.
+     * 
+     * <p>
+     * Defaults to an always false predicate.
+     * </p>
+     * 
+     * @param predicate the predicate, must be non-null
+     * 
+     * @since 5.2.0
+     */
+    public void setInResponseToIgnored(@Nonnull final Predicate<InOutOperationContext> predicate) {
+        inResponseToIgnored = Constraint.isNotNull(predicate, "InResponseTo ignored predicate was null");
+    }
+
+    /**
+     * Get the predicate which determines whether an Assertion SubjectConfirmationData Recipient is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate;
+     * </p>
+     * 
+     * @return the predicate
+     */
+    @Nonnull public Predicate<InOutOperationContext> getRecipientRequired() {
+        return recipientRequired;
+    }
+
+    /**
+     * Set the predicate which determines whether an Assertion SubjectConfirmationData Recipient is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate.
+     * </p>
+     * 
+     * @param predicate the predicate, must be non-null
+     */
+    public void setRecipientRequired(@Nonnull final Predicate<InOutOperationContext> predicate) {
+        recipientRequired = Constraint.isNotNull(predicate, "Recipient required predicate was null");
+    }
+
+    /**
+     * Get the predicate which determines whether an Assertion SubjectConfirmationData NotBefore is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate;
+     * </p>
+     * 
+     * @return the predicate
+     */
+    @Nonnull public Predicate<InOutOperationContext> getNotBeforeRequired() {
+        return notBeforeRequired;
+    }
+
+    /**
+     * Set the predicate which determines whether an Assertion SubjectConfirmationData NotBefore is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate.
+     * </p>
+     * 
+     * @param predicate the predicate, must be non-null
+     */
+    public void setNotBeforeRequired(@Nonnull final Predicate<InOutOperationContext> predicate) {
+        notBeforeRequired = Constraint.isNotNull(predicate, "NotBefore required predicate was null");
+    }
+
+    /**
+     * Get the predicate which determines whether an Assertion SubjectConfirmationData NotOnOrAfter is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate;
+     * </p>
+     * 
+     * @return the predicate
+     */
+    @Nonnull public Predicate<InOutOperationContext> getNotOnOrAfterRequired() {
+        return notOnOrAfterRequired;
+    }
+
+    /**
+     * Set the predicate which determines whether an Assertion SubjectConfirmationData NotOnOrAfter is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate.
+     * </p>
+     * 
+     * @param predicate the predicate, must be non-null
+     */
+    public void setNotOnOrAfterRequired(@Nonnull final Predicate<InOutOperationContext> predicate) {
+        notOnOrAfterRequired = Constraint.isNotNull(predicate, "NotOnOrAfter required predicate was null");
+    }
+
+    /**
+     * Get the predicate which determines whether an Assertion SubjectConfirmationData Address is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate;
+     * </p>
+     * 
+     * @return the predicate
+     */
+    @Nonnull public Predicate<InOutOperationContext> getAddressRequired() {
+        return addressRequired;
+    }
+
+    /**
+     * Set the predicate which determines whether an Assertion SubjectConfirmationData Address is required.
+     * 
+     * <p>
+     * Defaults to an always false predicate.
+     * </p>
+     * 
+     * @param predicate the predicate, must be non-null
+     */
+    public void setAddressRequired(final @Nonnull Predicate<InOutOperationContext> predicate) {
+        addressRequired = Constraint.isNotNull(predicate, "Address required predicate was null");
+    }
+
+    /**
+     * Get the predicate which determines whether an Assertion's network address(es) should be checked.
+     *
+     * <p>
+     * Defaults to an always true predicate;
+     * </p>
+     *
+     * @return the predicate
+     */
+    @Nonnull public Predicate<InOutOperationContext> getCheckAddress() {
+        return checkAddress;
+    }
+
+    /**
+     * Set the predicate which determines whether an Assertion's network address(es) should be checked.
+     *
+     * <p>
+     * Defaults to an always true predicate.
+     * </p>
+     *
+     * @param predicate the predicate, must be non-null
+     */
+    public void setCheckAddress(@Nonnull final Predicate<InOutOperationContext> predicate) {
+        checkAddress = Constraint.isNotNull(predicate, "Check address predicate was null");
+    }
+
+    /**
+     * Get the function for determining additional audience values.
+     *
+     * <p>
+     * Defaults to null.
+     * </p>
+     *
+     * @return the function
+     */
+    @Nullable public Function<InOutOperationContext,Set<String>> getAdditionalAudiences() {
+        return additionalAudiences;
+    }
+
+    /**
+     * Set the function for determining additional audience values.
+     *
+     * <p>
+     * Defaults to null.
+     * </p>
+     *
+     * @param function the function, may be null
+     */
+    public void setAdditionalAudiences(@Nullable final Function<InOutOperationContext,Set<String>> function) {
+        additionalAudiences = function;
+    }
+
+    /**
+     * Get the function for determining the valid Issuer values
+     *
+     * <p>
+     * Defaults to an implementation which resolves the outbound SAML peer entityID.
+     * </p>
+     *
+     * @return the function
+     */
+    @Nonnull public Function<InOutOperationContext,Set<String>> getValidIssuers() {
+        return validIssuers;
+    }
+
+    /**
+     * Set the function for determining the valid Issuer values
+     *
+     * <p>
+     * Defaults to an implementation which resolves the outbound SAML peer entityID.
+     * </p>
+     *
+     * @param function the function, may be null
+     */
+    public void setValidIssuers(@Nonnull final Function<InOutOperationContext,Set<String>> function) {
+        validIssuers = Constraint.isNotNull(function, "Valied Issuers function was null");
+    }
+    
+    /**
+     * Get the predicate which determines whether to require the Issuer contain the {@link NameIDType#ENTITY} Format.
+     * 
+     * @return predicate
+     * 
+     * @since 5.2.0
+     */
+    @Nonnull public Predicate<InOutOperationContext> getRequireEntityIssuer() {
+        return requireEntityIssuer;
+    }
+    
+    /**
+     * Get the predicate which determines whether to require the Issuer contain the {@link NameIDType#ENTITY} Format.
+     * 
+     * <p>Defaults to false.</p>
+     * 
+     * @param predicate the condition to set
+     * 
+     * @since 5.2.0
+     */
+    public void setRequireEntityIssuer(@Nonnull final Predicate<InOutOperationContext> predicate) {
+        requireEntityIssuer = Constraint.isNotNull(predicate, "Entity issuer predicate was null");
+    }
+
+    /**
+     * Get the function for determining the max allowed time since authentication.
+     *
+     * <p>
+     * Defaults to null.
+     * </p>
+     *
+     * @return the function
+     */
+    @Nullable public Function<InOutOperationContext,Duration> getMaximumTimeSinceAuthn() {
+        return maximumTimeSinceAuthn;
+    }
+
+    /**
+     * Set the function for determining the max allowed time since authentication.
+     *
+     * <p>
+     * Defaults to null.
+     * </p>
+     *
+     * @param function the function, may be null
+     */
+    public void setMaximumTimeSinceAuthn(@Nullable final Function<InOutOperationContext,Duration> function) {
+        maximumTimeSinceAuthn = function;
+    }
+
+    /**
+     * Get the function for resolving the signature validation CriteriaSet for a particular function.
+     * 
+     * <p>
+     * Defaults to: {@code null}.
+     * </p>
+     * 
+     * @return a criteria set instance, or null
+     */
+    @Nullable public Function<Pair<InOutOperationContext, Assertion>, CriteriaSet> getSignatureCriteriaSetFunction() {
+        return signatureCriteriaSetFunction;
+    }
+
+    /**
+     * Set the function for resolving the signature validation CriteriaSet for a particular function.
+     * 
+     * <p>
+     * Defaults to: {@code null}.
+     * </p>
+     * 
+     * @param function the resolving function, may be null
+     */
+    public void setSignatureCriteriaSetFunction(
+            @Nullable final Function<Pair<InOutOperationContext, Assertion>, CriteriaSet> function) {
+        signatureCriteriaSetFunction = function;
+    }
+
+    /** {@inheritDoc} */
+    @Nullable public ValidationContext apply(@Nullable final AssertionValidationInput input) {
+        if (input == null) {
+            return null;
+        }
+        
+        return new ValidationContext(buildStaticParameters(input));
+    }
+    
+    /**
+     * Build the static parameters map for input to the {@link ValidationContext}.
+     * 
+     * @param input the assertion validation input
+     * 
+     * @return the static parameters map
+     */
+    @Nonnull protected Map<String,Object> buildStaticParameters(
+            @Nonnull final AssertionValidationInput input) {
+        
+        final TreeMap<String, Object> staticParams = new TreeMap<>();
+        
+        // Clock skew
+        final var skewFunc = getClockSkew();
+        if (skewFunc != null) {
+            staticParams.put(SAML2AssertionValidationParameters.CLOCK_SKEW,
+                    skewFunc.apply(input.getOperationContext()));
+        }
+        
+        // Lifetime (for IssueInstant)
+        final var lifetimeFunc = getLifetime();
+        if (lifetimeFunc != null) {
+            staticParams.put(SAML2AssertionValidationParameters.LIFETIME,
+                    lifetimeFunc.apply(input.getOperationContext()));
+        }
+        
+        // Issuer
+        staticParams.put(SAML2AssertionValidationParameters.VALID_ISSUERS,
+                getValidIssuers().apply(input.getOperationContext()));
+        
+        staticParams.put(SAML2AssertionValidationParameters.REQUIRE_ENTITY_ISSUER,
+                Boolean.valueOf(getRequireEntityIssuer().test(input.getOperationContext())));
+        
+        // Signature
+        populateSignatureParameters(staticParams, input);
+        
+        // Conditions
+        populateConditionsParameters(staticParams, input);
+        
+        final Set<InetAddress> validAddresses = getValidAddresses(input);
+        final Boolean checkAddressEnabled = Boolean.valueOf(getCheckAddress().test(input.getOperationContext()));
+        assert checkAddressEnabled != null;
+        
+        // SubjectConfirmation
+        populateSubjectConfirmationParameters(staticParams, input, validAddresses, checkAddressEnabled);
+        
+        // Statements
+        populateStatementParams(staticParams, input, validAddresses, checkAddressEnabled);
+        
+        log.trace("Built static parameters map: {}", staticParams);
+        
+        return staticParams;
+    }
+
+
+    /**
+     *  Populate the static signature parameters.
+     * @param staticParams the parameters being populated
+     * @param input validation input
+     */
+    private void populateSignatureParameters(@Nonnull final Map<String, Object> staticParams,
+            @Nonnull final AssertionValidationInput input) {
+        
+        staticParams.put(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, 
+                Boolean.valueOf(getSignatureRequired().test(input.getOperationContext())));
+        staticParams.put(SAML2AssertionValidationParameters.SIGNATURE_VALIDATION_CRITERIA_SET, 
+                getSignatureCriteriaSet(input));
+        
+        final SecurityParametersContext securityParameters = getSecurityParametersLookupStrategy()
+                .apply(input.getOperationContext());
+        
+        final SignatureValidationParameters valParams = securityParameters != null
+                ? securityParameters.getSignatureValidationParameters() : null;
+        if (valParams != null) {
+            staticParams.put(SAML2AssertionValidationParameters.SIGNATURE_VALIDATION_TRUST_ENGINE,
+                    valParams.getSignatureTrustEngine());
+        }
+    }
+    
+    /**
+     * Populate the static Conditions parameters.
+     * @param staticParams the parameters being populated
+     * @param input validation input
+     */
+    private void populateConditionsParameters(@Nonnull final Map<String, Object> staticParams,
+            @Nonnull final AssertionValidationInput input) {
+        
+        // For general Conditions
+        staticParams.put(SAML2AssertionValidationParameters.COND_REQUIRED_CONDITIONS, getRequiredConditions(input));
+        
+        // For Audience Condition
+        staticParams.put(SAML2AssertionValidationParameters.COND_VALID_AUDIENCES, getValidAudiences(input));
+    }
+
+    /**
+     * Populate the static SubjectConfirmation parameters.
+     * 
+     * @param staticParams the parameters being populated
+     * @param input validation input
+     * @param validAddresses the valid addresses
+     * @param checkAddressEnabled whether address checking is enabled
+     */
+    private void populateSubjectConfirmationParameters(@Nonnull final Map<String, Object> staticParams,
+            @Nonnull final AssertionValidationInput input, @Nonnull final Set<InetAddress> validAddresses,
+            @Nonnull final Boolean checkAddressEnabled) {
+        
+        // For HoK subject confirmation
+        final X509Certificate attesterCertificate = getAttesterCertificate(input);
+        if (attesterCertificate != null) {
+            staticParams.put(SAML2AssertionValidationParameters.SC_HOK_PRESENTER_CERT, attesterCertificate);
+        }
+        final PublicKey attesterPublicKey = getAttesterPublicKey(input);
+        if (attesterPublicKey != null) {
+            staticParams.put(SAML2AssertionValidationParameters.SC_HOK_PRESENTER_KEY, attesterPublicKey);
+        }
+        
+        // For SubjectConfirmationData
+        staticParams.put(SAML2AssertionValidationParameters.SC_RECIPIENT_REQUIRED,
+                Boolean.valueOf(getRecipientRequired().test(input.getOperationContext())));
+        staticParams.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, getValidRecipients(input));
+        
+        staticParams.put(SAML2AssertionValidationParameters.SC_ADDRESS_REQUIRED,
+                Boolean.valueOf(getAddressRequired().test(input.getOperationContext())));
+        staticParams.put(SAML2AssertionValidationParameters.SC_VALID_ADDRESSES, validAddresses);
+        staticParams.put(SAML2AssertionValidationParameters.SC_CHECK_ADDRESS, checkAddressEnabled);
+        
+        staticParams.put(SAML2AssertionValidationParameters.SC_IN_RESPONSE_TO_IGNORED,
+                Boolean.valueOf(getInResponseToIgnored().test(input.getOperationContext())));
+        staticParams.put(SAML2AssertionValidationParameters.SC_IN_RESPONSE_TO_REQUIRED,
+                Boolean.valueOf(getInResponseToRequired().test(input.getOperationContext())));
+        
+        final var irtFunc = getInResponseTo();
+        if (irtFunc != null) {
+            staticParams.put(SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO,
+                    irtFunc.apply(input.getOperationContext()));
+        }
+        
+        staticParams.put(SAML2AssertionValidationParameters.SC_NOT_BEFORE_REQUIRED,
+                Boolean.valueOf(getNotBeforeRequired().test(input.getOperationContext())));
+        staticParams.put(SAML2AssertionValidationParameters.SC_NOT_ON_OR_AFTER_REQUIRED,
+                Boolean.valueOf(getNotOnOrAfterRequired().test(input.getOperationContext())));
+    }
+
+    /**
+     * Populate the static Statement params.
+     * @param staticParams the parameters being populated
+     * @param input validation input
+     * @param validAddresses the valid addresses
+     * @param checkAddressEnabled whether address checking is enabled
+     */
+    private void populateStatementParams(@Nonnull final Map<String, Object> staticParams,
+            @Nonnull final AssertionValidationInput input, @Nonnull final Set<InetAddress> validAddresses,
+            @Nonnull final Boolean checkAddressEnabled) {
+        
+        // For AuthnStatement
+        staticParams.put(SAML2AssertionValidationParameters.STMT_AUTHN_VALID_ADDRESSES, validAddresses);
+        staticParams.put(SAML2AssertionValidationParameters.STMT_AUTHN_CHECK_ADDRESS, checkAddressEnabled);
+        
+        final var maxTimeFunc = getMaximumTimeSinceAuthn();
+        if (maxTimeFunc != null) {
+            staticParams.put(SAML2AssertionValidationParameters.STMT_AUTHN_MAX_TIME, 
+                    maxTimeFunc.apply(input.getOperationContext()));
+        }
+    }
+    
+    /**
+     * Get the set of required Conditions.
+     * 
+     * <p>
+     * The default behavior is to return the locally-configured data via {@link #getRequiredConditions()}.
+     * </p>
+     * 
+     * @param input the assertion validation input
+     * 
+     * @return the set of required Condition names, may be null
+     */
+    @Nonnull protected Set<QName> getRequiredConditions(@Nonnull final AssertionValidationInput input) {
+        // Subclasses may override
+        return getRequiredConditions();
+    }
+
+    /**
+     * Get the signature validation criteria set.
+     * 
+     * <p>
+     * This implementation first evaluates the result of applying the function 
+     * {@link #getSignatureCriteriaSetFunction()}, if configured. If that evaluation did not
+     * produce an {@link EntityIdCriterion}, one is added based on the issuer of the {@link Assertion}.
+     * If that evaluation did not produce an instance of {@link UsageCriterion}, one is added with
+     * the value of {@link UsageType#SIGNING}.
+     * </p>
+     * 
+     * <p>
+     * Finally the following criteria are added if not already present and if the corresponding data
+     * is available in the inbound {@link MessageContext}:
+     * </p>
+     * <ul>
+     *  <li>{@link RoleDescriptorCriterion}</li>
+     *  <li>{@link EntityRoleCriterion}</li>
+     *  <li>{@link ProtocolCriterion}</li>
+     * </ul>
+     * 
+     * @param input the assertion validation input
+     *
+     * @return the criteria set based on the message context data
+     */
+    @Nonnull protected CriteriaSet getSignatureCriteriaSet(@Nonnull final AssertionValidationInput input) {
+        final CriteriaSet criteriaSet = new CriteriaSet();
+        
+        final var sigCritFunc = getSignatureCriteriaSetFunction();
+        if (sigCritFunc != null) {
+            final CriteriaSet dynamicCriteria = sigCritFunc.apply(
+                    new Pair<>(input.getOperationContext(), input.getAssertion()));
+            if (dynamicCriteria != null) {
+                criteriaSet.addAll(dynamicCriteria);
+            }
+        }
+        
+        if (!criteriaSet.contains(EntityIdCriterion.class)) {
+            final Issuer issuerObj = input.getAssertion().getIssuer();
+            if (issuerObj != null) {
+                final String issuer = StringSupport.trimOrNull(issuerObj.getValue());
+                if (issuer != null) {
+                    log.debug("Adding internally-generated EntityIdCriterion with value of: {}", issuer);
+                    criteriaSet.add(new EntityIdCriterion(issuer));
+                }
+            }
+        }
+        
+        if (!criteriaSet.contains(UsageCriterion.class)) {
+            log.debug("Adding internally-generated UsageCriterion with value of: {}", UsageType.SIGNING);
+            criteriaSet.add(new UsageCriterion(UsageType.SIGNING));
+        }
+        
+        final MessageContext inboundContext = input.getOperationContext().getInboundMessageContext();
+        if (inboundContext != null) {
+            populateSignatureCriteriaFromInboundContext(criteriaSet, inboundContext);
+        }
+
+        log.debug("Resolved Signature validation CriteriaSet: {}", criteriaSet);
+        
+        return criteriaSet;
+    }
+
+    /**
+     * Populate signature criteria from the specified {@link MessageContext}.
+     *
+     * <ul>
+     *  <li>{@link RoleDescriptorCriterion}</li>
+     *  <li>{@link EntityRoleCriterion}</li>
+     *  <li>{@link ProtocolCriterion}</li>
+     * </ul>
+     *
+     * @param criteriaSet the criteria set to populate
+     * @param inboundContext the inbound message context
+     */
+    // Checkstyle: CyclomaticComplexity OFF
+    protected void populateSignatureCriteriaFromInboundContext(@Nonnull final CriteriaSet criteriaSet,
+            @Nonnull final MessageContext inboundContext) {
+
+        final SAMLPeerEntityContext peerContext = inboundContext.getSubcontext(SAMLPeerEntityContext.class);
+        if (peerContext != null) {
+            if (!criteriaSet.contains(RoleDescriptorCriterion.class)) {
+                final SAMLMetadataContext metadataContext = peerContext.getSubcontext(SAMLMetadataContext.class);
+                if (metadataContext != null) {
+                    final RoleDescriptor role = metadataContext.getRoleDescriptor();
+                    if (role != null) {
+                        criteriaSet.add(new RoleDescriptorCriterion(role));
+                    }
+                }
+            }
+            if (!criteriaSet.contains(EntityRoleCriterion.class)) {
+                final QName role = peerContext.getRole();
+                if (role != null) {
+                    criteriaSet.add(new EntityRoleCriterion(role));
+                }
+            }
+        }
+
+        final SAMLProtocolContext protocolContext = inboundContext.getSubcontext(SAMLProtocolContext.class);
+        if (!criteriaSet.contains(ProtocolCriterion.class) && protocolContext != null) {
+            final String protocol = protocolContext.getProtocol();
+            if (protocol != null) {
+                criteriaSet.add(new ProtocolCriterion(protocol));
+            }
+        }
+        
+        if (!criteriaSet.contains(SignatureValidationParametersCriterion.class)) {
+            final SecurityParametersContext secParamsContext =
+                    inboundContext.getSubcontext(SecurityParametersContext.class);
+            if (secParamsContext != null) {
+                final SignatureValidationParameters valParams = secParamsContext.getSignatureValidationParameters();
+                if (valParams != null) {
+                    criteriaSet.add(new SignatureValidationParametersCriterion(valParams));
+                }
+            }
+        }
+    }
+    // Checkstyle: CyclomaticComplexity ON
+
+    /**
+     * Get the attesting entity's {@link X509Certificate}.
+     * 
+     * <p>
+     * This implementation returns the client TLS certificate present in the 
+     * {@link jakarta.servlet.http.HttpServletRequest}, or null if one is not present.
+     * </p>
+     * 
+     * @param input the assertion validation input
+     * 
+     * @return the entity certificate, or null
+     */
+    @Nullable protected X509Certificate getAttesterCertificate(
+            @Nonnull final AssertionValidationInput input) {
+        final X509Certificate cert = input.getNetworkInformationSupplier().getAttesterCertificate();
+        if (cert != null) {
+            return cert;
+        }
+
+        log.debug("Peer TLS X.509 certificate was not present. " 
+                + "Holder-of-key proof-of-possession via client TLS cert will not be possible");
+        return null;
+    }
+
+    /**
+     * Get the attesting entity's {@link PublicKey}.
+     * 
+     * <p>
+     * This implementation returns null. Subclasses should override to implement specific logic.
+     * </p>
+     * 
+     * @param input the assertion validation input
+     * 
+     * @return the entity public key, or null
+     */
+    @Nullable protected PublicKey getAttesterPublicKey(@Nonnull final AssertionValidationInput input) {
+        return null;
+    }
+    
+    /**
+     * Get the valid recipient endpoints for attestation.
+     * 
+     * <p>
+     * This implementation returns a set containing the 2 values;
+     * </p>
+     * <ol>
+     * <li>
+     * the result of evaluating
+     * {@link SAMLBindingSupport#getActualReceiverEndpointURI(MessageContext, HttpServletRequest)}
+     * </li>
+     * <li>
+     * if enabled via the eval of {@link #getIncludeSelfEntityIDAsRecipient()}, the value from evaluating
+     * {@link #getSelfEntityID(AssertionValidationInput)} if non-null
+     * 
+     * </li>
+     * </ol>
+     * 
+     * @param input the assertion validation input
+     * 
+     * @return set of recipient endpoint URI's
+     */
+    @Nonnull @Unmodifiable @NotLive protected Set<String> getValidRecipients(
+            @Nonnull final AssertionValidationInput input) {
+        final LazySet<String> validRecipients = new LazySet<>();
+        
+        try {
+            final String contextEndpoint = SAMLBindingSupport.getActualReceiverEndpointURI(
+                    input.getOperationContext().ensureInboundMessageContext());
+            final String endpoint = contextEndpoint != null ? contextEndpoint
+                    : input.getNetworkInformationSupplier().getReceiverEndpointURI();
+            if (endpoint != null) {
+                validRecipients.add(endpoint);
+            }
+        } catch (final MessageException e) {
+            log.warn("Attempt to resolve recipient endpoint failed", e);
+        }
+        
+        if (getIncludeSelfEntityIDAsRecipient().test(input.getOperationContext())) {
+            final String selfEntityID = getSelfEntityID(input);
+            if (selfEntityID != null) {
+                validRecipients.add(selfEntityID);
+            }
+        }
+        
+        log.debug("Resolved valid subject confirmation recipients set: {}", validRecipients);
+        return validRecipients;
+    }
+
+    /**
+     * Get the set of addresses which are valid for subject confirmation.
+     * 
+     * <p>
+     * This implementation simply returns the set based on 
+     * {@link #getAttesterIPAddress(AssertionValidationInput)}, if that produces a value.
+     * Otherwise an empty set is returned.
+     * </p>
+     * 
+     * @param input the assertion validation input
+     * 
+     * @return the set of valid addresses
+     */
+    @Nonnull @Unmodifiable @NotLive protected Set<InetAddress> getValidAddresses(
+            @Nonnull final AssertionValidationInput input) {
+        try {
+            final LazySet<InetAddress> validAddresses = new LazySet<>();
+            InetAddress[] addresses = null;
+            final String attesterIPAddress = getAttesterIPAddress(input);
+            log.debug("Saw attester IP address: {}", attesterIPAddress);
+            if (attesterIPAddress != null) {
+                addresses = InetAddress.getAllByName(attesterIPAddress);
+                validAddresses.addAll(Arrays.asList(addresses));
+                log.debug("Resolved valid subject confirmation InetAddress set: {}", validAddresses);
+                return validAddresses;
+            }
+            log.warn("Could not determine attester IP address. Validation of Assertion may or may not succeed");
+            return CollectionSupport.emptySet();
+        } catch (final UnknownHostException e) {
+            log.warn("Processing of attester IP address failed. Validation of Assertion may or may not succeed", e);
+            return CollectionSupport.emptySet();
+        }
+    }
+    
+    /**
+     * Get the attester's IP address.
+     * 
+     * <p>
+     * This implementation returns the value of {@link jakarta.servlet.http.HttpServletRequest#getRemoteAddr()}.
+     * </p>
+     * 
+     * @param input the assertion validation input
+     * 
+     * @return the IP address of the attester
+     */
+    @Nullable protected String getAttesterIPAddress(@Nonnull final AssertionValidationInput input) {
+        return input.getNetworkInformationSupplier().getAttesterIPAddress();
+    }
+    
+    /**
+     * Get the valid audiences for attestation.
+     * 
+     * <p>
+     * This implementation returns a set containing the union of:
+     * </p>
+     * <ol>
+     * <li>the result of {@link #getSelfEntityID(AssertionValidationInput)}, if non-null</li>
+     * <li>the result of evaluating {@link #getAdditionalAudiences()}, if non-null</li>
+     * </ol>
+     * 
+     * @param input the assertion validation input
+     * 
+     * @return set of audience URI's
+     */
+    @Nonnull @Unmodifiable @NotLive protected Set<String> getValidAudiences(
+            @Nonnull final AssertionValidationInput input) {
+        final LazySet<String> validAudiences = new LazySet<>();
+        
+        final String selfEntityID = getSelfEntityID(input);
+        if (selfEntityID != null) {
+            validAudiences.add(selfEntityID);
+        }
+        
+        final var audFunc = getAdditionalAudiences();
+        if (audFunc != null) {
+            final Set<String> additional = audFunc.apply(input.getOperationContext());
+            if (additional != null) {
+                validAudiences.addAll(additional);
+            }
+        }
+        
+        log.debug("Resolved valid audiences set: {}", validAudiences);
+        return validAudiences;
+    }
+    
+    /**
+     * Get the self entityID.
+     * 
+     * @param input the assertion validation input
+     * 
+     * @return the self entityID, or null if could not be resolved
+     */
+    @Nullable protected String getSelfEntityID(@Nonnull final AssertionValidationInput input) {
+        final SAMLSelfEntityContext selfContext = input.getOperationContext()
+                .ensureInboundMessageContext()
+                .getSubcontext(SAMLSelfEntityContext.class);
+        
+        if (selfContext != null) {
+            return selfContext.getEntityId();
+        }
+        
+        return null;
+    }
+    
+    /** Default strategy for resolving the valid InResponseTo value. */
+    public static class DefaultValidInResponseToLookupFunction implements Function<InOutOperationContext, String> {
+
+        /** The lookup delegate. */
+        @Nonnull private Function<MessageContext, String> delegate;
+
+        /** Constructor. */
+        public DefaultValidInResponseToLookupFunction() {
+            delegate = new SAMLMessageInfoContextIDFunction().compose(
+                    new ChildContextLookup<>(SAMLMessageInfoContext.class, true).compose(
+                            new MessageContextLookup<>(Direction.OUTBOUND)));
+        }
+
+        /** {@inheritDoc} */
+        @Nullable public String apply(@Nullable final InOutOperationContext prc) {
+            if (prc == null || prc.getInboundMessageContext() == null) {
+                return null;
+            }
+
+            //Note: Doesn't matter whether we apply to inbound or outbound
+            return delegate.apply(prc.getInboundMessageContext());
+        }
+
+    }
+    
+    /** 
+     * Default strategy for resolving the valid Issuers.
+     * 
+     * <p>
+     * Resolves the entityID from the {@link SAMLPeerEntityContext} child of the outbound {@link MessageContext}.
+     * </p>
+     * */
+    public static class DefaultValidIssuersLookupFunction implements Function<InOutOperationContext, Set<String>> {
+        
+        /** The lookup delegate. */
+        @Nonnull private Function<MessageContext, String> delegate;
+
+        /** Constructor. */
+        public DefaultValidIssuersLookupFunction() {
+            delegate = new SAMLEntityIDFunction().compose(
+                    new ChildContextLookup<>(SAMLPeerEntityContext.class).compose(
+                            new MessageContextLookup<>(Direction.OUTBOUND)));
+        }
+
+        /** {@inheritDoc} */
+        @Nullable @Unmodifiable @NotLive public Set<String> apply(@Nullable final InOutOperationContext prc) {
+            if (prc == null || prc.getInboundMessageContext() == null) {
+                return null;
+            }
+            
+            // Note: Doesn't matter whether we apply to inbound or outbound
+            final String entityID = delegate.apply(prc.getInboundMessageContext());
+            if (entityID != null) {
+                return CollectionSupport.singleton(entityID);
+            }
+            return CollectionSupport.emptySet();
+        }
+        
+    }
+
+}
\ No newline at end of file
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/messaging/impl/package-info.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/messaging/impl/package-info.java
new file mode 100644
index 000000000..8cf3bdc3d
--- /dev/null
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/messaging/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Implementation classes related to networking and messaging APIs that support validating SAML 2 Assertions.
+
+ */
+ at NonnullElements
+package org.opensaml.saml.saml2.assertion.messaging.impl;
+
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/profile/SAML2ObjectSupportTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/profile/SAML2ObjectSupportTest.java
new file mode 100644
index 000000000..6dee4b8e9
--- /dev/null
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/profile/SAML2ObjectSupportTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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 org.opensaml.saml.saml2.profile;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.core.testing.XMLObjectBaseTestCase;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.saml.saml2.core.EncryptedID;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.Subject;
+import org.opensaml.saml.saml2.core.tests.MockBaseID;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ *
+ */
+public class SAML2ObjectSupportTest extends XMLObjectBaseTestCase {
+    
+    @DataProvider
+    public Object[][] subjects () {
+       return new Object[][] {
+           new Object[] {
+                   "luke", NameID.UNSPECIFIED, null, null,
+                   "luke", NameID.UNSPECIFIED, null, null,
+                   Boolean.TRUE},
+           new Object[] {
+                   "luke", null, null, null,
+                   "luke", NameID.UNSPECIFIED, null, null,
+                   Boolean.TRUE},
+           new Object[] {
+                   "luke", NameID.UNSPECIFIED, null, null,
+                   "luke", null, null, null,
+                   Boolean.TRUE},
+           new Object[] {
+                   "luke", null, null, null,
+                   "luke", null, null, null,
+                   Boolean.TRUE},
+           new Object[] {
+                   "luke", null, "QualFoo", "SPQualFoo",
+                   "luke", null, "QualFoo", "SPQualFoo",
+                   Boolean.TRUE},
+
+           new Object[] {
+                   "luke", null, null, null,
+                   "han", null, null, null,
+                   Boolean.FALSE},
+           new Object[] {
+                   "luke", "FormatFoo", null, null,
+                   "luke", "FormatBar", null, null,
+                   Boolean.FALSE},
+           new Object[] {
+                   "luke", null, "QualFoo", null,
+                   "luke", null, "QualBar", null,
+                   Boolean.FALSE},
+           new Object[] {
+                   "luke", null, null, "SPQualFoo",
+                   "luke", null, null, "SPQualBar",
+                   Boolean.FALSE},
+       };
+    }
+    
+    @Test(dataProvider="subjects")
+    public void matchSubject(String targetValue, String targetFormat, String targetNameQualifer, String targetSPNameQualifer,
+                             String controlValue, String controlFormat, String controlNameQualifer, String controlSPNameQualifer,
+                             Boolean matches) {
+        
+        Assert.assertEquals(SAML2ObjectSupport.matchSubject(
+                buildSubject(targetValue, targetFormat, targetNameQualifer, targetSPNameQualifer),
+                buildSubject(controlValue, controlFormat, controlNameQualifer, controlSPNameQualifer)),
+                matches);
+    }
+    
+    @Test
+    public void matchSubjectSpecialCases() {
+        Subject subj1 = buildSubject("luke", null, null, null);
+        Subject subj2 = buildSubject("luke", null, null, null);
+        
+        Assert.assertEquals(SAML2ObjectSupport.matchSubject(subj1, subj2), Boolean.TRUE);
+        
+        subj1.setNameID(null);
+        subj2 = buildSubject("luke", null, null, null);
+        Assert.assertEquals(SAML2ObjectSupport.matchSubject(subj1, subj2), Boolean.FALSE);
+        
+        subj1 = buildSubject("luke", null, null, null);
+        subj2.setNameID(null);
+        Assert.assertEquals(SAML2ObjectSupport.matchSubject(subj1, subj2), Boolean.FALSE);
+        
+        subj1.setNameID(null);
+        subj2.setNameID(null);
+        Assert.assertEquals(SAML2ObjectSupport.matchSubject(subj1, subj2), Boolean.TRUE);
+        
+        subj2 = buildSubject("luke", null, null, null);
+
+        subj1 = (Subject) XMLObjectSupport.buildXMLObject(Subject.DEFAULT_ELEMENT_NAME);
+        subj1.setEncryptedID((EncryptedID) XMLObjectSupport.buildXMLObject(EncryptedID.DEFAULT_ELEMENT_NAME));
+        try {
+            SAML2ObjectSupport.matchSubject(subj1, subj2);
+            Assert.fail("Subject match did not fail on presence of EncryptedID");
+        } catch (IllegalArgumentException e) {
+            //expected
+        }
+        
+        subj1 = (Subject) XMLObjectSupport.buildXMLObject(Subject.DEFAULT_ELEMENT_NAME);
+        subj1.setBaseID(new MockBaseID());
+        try {
+            SAML2ObjectSupport.matchSubject(subj1, subj2);
+            Assert.fail("Subject match did not fail on presence of BaseID");
+        } catch (IllegalArgumentException e) {
+            //expected
+        }
+        
+    }
+    
+    @Nonnull
+    private Subject buildSubject(String value, String format, String nameQualifer, String spNameQualifer) {
+        
+        final NameID nameID = (NameID) XMLObjectSupport.buildXMLObject(NameID.DEFAULT_ELEMENT_NAME);
+        nameID.setValue(value);
+        nameID.setFormat(format);
+        nameID.setNameQualifier(nameQualifer);
+        nameID.setSPNameQualifier(spNameQualifer);
+
+        final Subject subject = (Subject) XMLObjectSupport.buildXMLObject(Subject.DEFAULT_ELEMENT_NAME);
+        subject.setNameID(nameID);
+        return subject;
+    }
+
+}

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


More information about the commits mailing list