[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