[java-plugin-shibd-saml] branch main updated: Reimplement Recipient and NotOnOrAfter checks as profile settings.

Codeberg noreply at shibboleth.net
Mon Apr 6 21:20:14 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-saml.

View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-saml/commit/3ecddce083d45ae2aca341d307c13010eb5c409d

The following commit(s) were added to refs/heads/main by this push:
     new 3ecddce  Reimplement Recipient and NotOnOrAfter checks as profile settings.
3ecddce is described below

commit 3ecddce083d45ae2aca341d307c13010eb5c409d
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Apr 6 17:19:55 2026 -0400

    Reimplement Recipient and NotOnOrAfter checks as profile settings.
---
 .../config/BrowserSSOProfileConfiguration.java     | 30 ++++++++++++
 .../config/logic/AllowUnsolicitedPredicate.java    |  2 -
 ...cate.java => RequireNotOnOrAfterPredicate.java} | 12 ++---
 ...edicate.java => RequireRecipientPredicate.java} | 12 ++---
 .../idp/flows/sp/consumer/saml2/saml2-beans.xml    |  8 +++-
 .../net/shibboleth/sp/service/agent/postconfig.xml |  2 +
 .../shibboleth/idp/module/conf/sp/saml.properties  |  7 +--
 .../flows/saml2/SAML2TokenConsumerFlowTest.java    | 50 ++++++++++++++++++++
 .../impl/BrowserSSOProfileConfiguration.java       | 55 ++++++++++++++++++++++
 9 files changed, 157 insertions(+), 21 deletions(-)

diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
index f560362..d1f7a47 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
@@ -50,6 +50,36 @@ public interface BrowserSSOProfileConfiguration extends SAMLArtifactConsumerProf
     @ConfigurationSetting(name="allowUnsolicited")
     boolean isAllowUnsolicited(@Nullable final ProfileRequestContext profileRequestContext);
 
+    /**
+     * Get whether the inbound {@link SubjectConfirmationData#getRecipient()} value
+     * may be empty.
+     * 
+     * <p>Its presence is required by the profile, so this is provided as a bug workaround.</p>
+     * 
+     * <p>Note that this does not allow it to be present but invalid.</p>
+     * 
+     * @param profileRequestContext current profile request context
+     * 
+     * @return whether to enforce the presence of Recipient
+     */
+    @ConfigurationSetting(name="requireRecipient")
+    boolean isRequireRecipient(@Nullable final ProfileRequestContext profileRequestContext);
+
+    /**
+     * Get whether the inbound {@link SubjectConfirmationData#getNotOnOrAfter()} value
+     * may be empty.
+     * 
+     * <p>Its presence is required by the profile, so this is provided as a bug workaround.</p>
+     * 
+     * <p>Note that this does not allow it to be present but invalid.</p>
+     * 
+     * @param profileRequestContext current profile request context
+     * 
+     * @return whether to enforce the presence of Recipient
+     */
+    @ConfigurationSetting(name="requireNotOnOrAfter")
+    boolean isRequireNotOnOrAfter(@Nullable final ProfileRequestContext profileRequestContext);
+    
     /**
      * Get the {@link AuthnContextClassRef} values to include in SAML request.
      * 
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/AllowUnsolicitedPredicate.java b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/AllowUnsolicitedPredicate.java
index e61af9c..a2776cf 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/AllowUnsolicitedPredicate.java
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/AllowUnsolicitedPredicate.java
@@ -27,8 +27,6 @@ import org.opensaml.profile.context.ProfileRequestContext;
  * setting of {@link BrowserSSOProfileConfiguration#isAllowUnsolicited(ProfileRequestContext)}.
  * 
  * <p>Defaults to false.</p>
- * 
- * @since 5.2.0
  */
 public class AllowUnsolicitedPredicate extends AbstractRelyingPartyPredicate {
     
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/AllowUnsolicitedPredicate.java b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/RequireNotOnOrAfterPredicate.java
similarity index 82%
copy from sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/AllowUnsolicitedPredicate.java
copy to sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/RequireNotOnOrAfterPredicate.java
index e61af9c..1d62ec0 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/AllowUnsolicitedPredicate.java
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/RequireNotOnOrAfterPredicate.java
@@ -24,13 +24,11 @@ import org.opensaml.profile.context.ProfileRequestContext;
 
 /**
  * A predicate that evaluates a {@link ProfileRequestContext} and extracts the effective
- * setting of {@link BrowserSSOProfileConfiguration#isAllowUnsolicited(ProfileRequestContext)}.
+ * setting of {@link BrowserSSOProfileConfiguration#isRequireNotOnOrAfter(ProfileRequestContext)}.
  * 
- * <p>Defaults to false.</p>
- * 
- * @since 5.2.0
+ * <p>Defaults to true.</p>
  */
-public class AllowUnsolicitedPredicate extends AbstractRelyingPartyPredicate {
+public class RequireNotOnOrAfterPredicate extends AbstractRelyingPartyPredicate {
     
     /** {@inheritDoc} */
     public boolean test(@Nullable final ProfileRequestContext input) {
@@ -38,11 +36,11 @@ public class AllowUnsolicitedPredicate extends AbstractRelyingPartyPredicate {
         final RelyingPartyContext rpc = getRelyingPartyContext(input);
         if (rpc != null) {
             if (rpc.getProfileConfig() instanceof BrowserSSOProfileConfiguration sso) {
-                return sso.isAllowUnsolicited(input);
+                return sso.isRequireNotOnOrAfter(input);
             }
         }
         
-        return false;
+        return true;
     }
 
 }
\ No newline at end of file
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/AllowUnsolicitedPredicate.java b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/RequireRecipientPredicate.java
similarity index 82%
copy from sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/AllowUnsolicitedPredicate.java
copy to sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/RequireRecipientPredicate.java
index e61af9c..3eae3b8 100644
--- a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/AllowUnsolicitedPredicate.java
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/logic/RequireRecipientPredicate.java
@@ -24,13 +24,11 @@ import org.opensaml.profile.context.ProfileRequestContext;
 
 /**
  * A predicate that evaluates a {@link ProfileRequestContext} and extracts the effective
- * setting of {@link BrowserSSOProfileConfiguration#isAllowUnsolicited(ProfileRequestContext)}.
+ * setting of {@link BrowserSSOProfileConfiguration#isRequireRecipient(ProfileRequestContext)}.
  * 
- * <p>Defaults to false.</p>
- * 
- * @since 5.2.0
+ * <p>Defaults to true.</p>
  */
-public class AllowUnsolicitedPredicate extends AbstractRelyingPartyPredicate {
+public class RequireRecipientPredicate extends AbstractRelyingPartyPredicate {
     
     /** {@inheritDoc} */
     public boolean test(@Nullable final ProfileRequestContext input) {
@@ -38,11 +36,11 @@ public class AllowUnsolicitedPredicate extends AbstractRelyingPartyPredicate {
         final RelyingPartyContext rpc = getRelyingPartyContext(input);
         if (rpc != null) {
             if (rpc.getProfileConfig() instanceof BrowserSSOProfileConfiguration sso) {
-                return sso.isAllowUnsolicited(input);
+                return sso.isRequireRecipient(input);
             }
         }
         
-        return false;
+        return true;
     }
 
 }
\ No newline at end of file
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-beans.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-beans.xml
index 08b429e..b502bab 100644
--- a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-beans.xml
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/saml2/saml2-beans.xml
@@ -205,8 +205,12 @@
                 <util:constant static-field="org.opensaml.saml.saml2.core.AudienceRestriction.DEFAULT_ELEMENT_NAME" />
             </set>
         </property>
-        <property name="recipientRequired" value="%{sp.saml.checkRecipient:true}" />
-        <property name="notOnOrAfterRequired" value="%{sp.saml.checkNotOnOrAfter:true}" />
+        <property name="recipientRequired">
+            <bean class="net.shibboleth.sp.saml.saml2.profile.config.logic.RequireRecipientPredicate" />
+        </property>
+        <property name="notOnOrAfterRequired">
+            <bean class="net.shibboleth.sp.saml.saml2.profile.config.logic.RequireNotOnOrAfterPredicate" />
+        </property>
     </bean>
 
     <bean id="ValidateAssertions"
diff --git a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
index 4fa5c27..0e2882b 100644
--- a/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
+++ b/sp-saml-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
@@ -64,6 +64,8 @@
         p:maximumTimeSinceAuthn="%{sp.saml.maxTimeSinceAuthn:0}"
         p:checkAddressPredicate="%{sp.saml.checkAddress:true}"
         p:checkInResponseToPredicate="%{sp.saml.checkInResponseTo:true}"
+        p:requireRecipientPredicate="%{sp.saml.requireRecipient:true}"
+        p:requireNotOnOrAfterPredicate="%{sp.saml.requireNotOnOrAfter:true}"
         p:allowUnsolicitedPredicate="%{sp.saml.allowUnsolicited:true}"
         p:extractStandardAttributesPredicate="%{sp.saml.extractStandardAttributes:false}"
         p:resolveAttributesPredicate="%{sp.saml.resolveAttributes:false}" />
diff --git a/sp-saml-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/saml.properties b/sp-saml-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/saml.properties
index b5cd27a..2e6aeec 100644
--- a/sp-saml-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/saml.properties
+++ b/sp-saml-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/saml.properties
@@ -12,9 +12,10 @@ sp.saml.encryption.cert = %{idp.home}/credentials/sp/sp-encryption.crt
 
 # Global profile defaults
 #sp.saml.encryption.optional = true
-# TODO: The next two are global only at the moment.
-#sp.saml.checkRecipient = true
-#sp.saml.checkNotOnOrAfter = true
+# Turn off to allow SubjectConfirmationData/@Recipient to be absent
+#sp.saml.requireRecipient = true
+# Turn off to allow SubjectConfirmationData/@NotOnOrAfter to be absent
+#sp.saml.requireNotOnOrAfter = true
 # Turn off to disallow unsolicited SSO responses
 #sp.saml.allowUnsolicited = true
 # Turn off to disable address check during assertion validation
diff --git a/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2TokenConsumerFlowTest.java b/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2TokenConsumerFlowTest.java
index 9df97d5..b23b78b 100644
--- a/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2TokenConsumerFlowTest.java
+++ b/sp-saml-conf-impl/src/test/java/net/shibboleth/sp/saml/flows/saml2/SAML2TokenConsumerFlowTest.java
@@ -311,6 +311,56 @@ public class SAML2TokenConsumerFlowTest extends AbstractSPFlowTest {
                 response.getAssertions().get(0).getID()));
     }
 
+    /**
+     * Test flow with no confirmation expiration.
+     * 
+     * @throws IOException 
+     */
+    @Test
+    public void testNoNotOnOrAfter() throws IOException {
+        final Response response = buildSAMLResponse(ISSUER, StatusCode.SUCCESS);
+        final Subject subject = response.getAssertions().get(0).getSubject();
+        assert subject != null;
+        final SubjectConfirmationData data = subject.getSubjectConfirmations().get(0).getSubjectConfirmationData();
+        assert data != null;
+        data.setNotOnOrAfter(null);
+        sign(response);
+        final DDF input = buildRemotedPOSTResponse(response);
+        setApplicationRequest(APPLICATION_ID, input);
+
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        final DDF output = assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
+        validateAssertionError(output, String.format("SubjectConfirmationData/@NotOnOrAfter was missing and was required",
+                response.getAssertions().get(0).getID(), RESPONSE_URL));
+    }
+    
+    /**
+     * Test flow with no confirmation recipient.
+     * 
+     * @throws IOException 
+     */
+    @Test
+    public void testNoRecipient() throws IOException {
+        final Response response = buildSAMLResponse(ISSUER, StatusCode.SUCCESS);
+        final Subject subject = response.getAssertions().get(0).getSubject();
+        assert subject != null;
+        final SubjectConfirmationData data = subject.getSubjectConfirmations().get(0).getSubjectConfirmationData();
+        assert data != null;
+        data.setRecipient(null);
+        sign(response);
+        final DDF input = buildRemotedPOSTResponse(response);
+        setApplicationRequest(APPLICATION_ID, input);
+
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        final DDF output = assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
+        validateAssertionError(output, String.format("SubjectConfirmationData/@Recipient was missing and was required",
+                response.getAssertions().get(0).getID(), RESPONSE_URL));
+    }
+    
     /**
      * Test flow with bad confirmation recipient.
      * 
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/BrowserSSOProfileConfiguration.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/BrowserSSOProfileConfiguration.java
index fd62d39..061b0b6 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/BrowserSSOProfileConfiguration.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/config/impl/BrowserSSOProfileConfiguration.java
@@ -41,6 +41,7 @@ import org.opensaml.saml.common.xml.SAMLConstants;
 import org.opensaml.saml.saml2.core.AuthnContextClassRef;
 import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
 import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.SubjectConfirmationData;
 import org.opensaml.saml.saml2.core.SubjectLocality;
 import org.opensaml.saml.saml2.metadata.RequestedAttribute;
 
@@ -66,6 +67,12 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2AssertionConsum
     /** Whether to validate inbound InResponseTo values. */
     @Nonnull private Predicate<ProfileRequestContext> checkInResponseToPredicate;
 
+    /** Whether to require Recipient value. */
+    @Nonnull private Predicate<ProfileRequestContext> requireRecipientPredicate;
+
+    /** Whether to require NotOnOrAfter value. */
+    @Nonnull private Predicate<ProfileRequestContext> requireNotOnOrAfterPredicate;
+    
     /** Whether to allow empty InResponseTo values. */
     @Nonnull private Predicate<ProfileRequestContext> allowUnsolicitedPredicate;
 
@@ -125,6 +132,8 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2AssertionConsum
         forceAuthnPredicate = PredicateSupport.alwaysFalse();
         checkAddressPredicate = PredicateSupport.alwaysTrue();
         checkInResponseToPredicate = PredicateSupport.alwaysTrue();
+        requireRecipientPredicate = PredicateSupport.alwaysTrue();
+        requireNotOnOrAfterPredicate = PredicateSupport.alwaysTrue();
         allowUnsolicitedPredicate = PredicateSupport.alwaysTrue();
         maximumTimeSinceAuthnLookupStrategy = FunctionSupport.constant(null);
         proxyCountLookupStrategy = FunctionSupport.constant(null);
@@ -300,6 +309,52 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2AssertionConsum
         checkInResponseToPredicate = Constraint.isNotNull(condition, "InResponseTo checking predicate cannot be null");
     }
     
+    /** {@inheritDoc} */
+    public boolean isRequireRecipient(@Nullable final ProfileRequestContext profileRequestContext) {
+        return requireRecipientPredicate.test(profileRequestContext);
+    }
+    
+    /**
+     * Sets whether to require {@link SubjectConfirmationData#getRecipient()} be non-null.
+     * 
+     * @param flag flag to set
+     */
+    public void setRequireRecipient(final boolean flag) {
+        requireRecipientPredicate = PredicateSupport.constant(flag);
+    }
+
+    /**
+     * Sets the condition for whether to require {@link SubjectConfirmationData#getRecipient()} be non-null.
+     * 
+     * @param condition condition to set
+     */
+    public void setRequireRecipientPredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
+        requireRecipientPredicate = Constraint.isNotNull(condition, "Require Recipient predicate cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    public boolean isRequireNotOnOrAfter(@Nullable final ProfileRequestContext profileRequestContext) {
+        return requireNotOnOrAfterPredicate.test(profileRequestContext);
+    }
+
+    /**
+     * Sets whether to require {@link SubjectConfirmationData#getNotOnOrAfter()} be non-null.
+     * 
+     * @param flag flag to set
+     */
+    public void setRequireNotOnOrAfter(final boolean flag) {
+        requireNotOnOrAfterPredicate = PredicateSupport.constant(flag);
+    }
+
+    /**
+     * Sets the condition for whether to require {@link SubjectConfirmationData#getNotOnOrAfter()} be non-null.
+     * 
+     * @param condition condition to set
+     */
+    public void setRequireNotOnOrAfterPredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
+        requireNotOnOrAfterPredicate = Constraint.isNotNull(condition, "Require NotOnOrAfter predicate cannot be null");
+    }
+    
     /** {@inheritDoc} */
     public boolean isAllowUnsolicited(@Nullable final ProfileRequestContext profileRequestContext) {
         return allowUnsolicitedPredicate.test(profileRequestContext);

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


More information about the commits mailing list