[java-plugin-shibd] branch main updated: Continued WIP on SAML initiator.
Scott Cantor
cantor.2 at osu.edu
Tue Aug 6 19:32:15 UTC 2024
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-plugin-shibd.
View the commit online:
http://git.shibboleth.net/view/?p=java-plugin-shibd.git;a=commit;h=152321052a7f81a904806cf89410442380bf1139
The following commit(s) were added to refs/heads/main by this push:
new 1523210 Continued WIP on SAML initiator.
1523210 is described below
commit 152321052a7f81a904806cf89410442380bf1139
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Aug 6 15:32:11 2024 -0400
Continued WIP on SAML initiator.
---
.../idp/flows/sp/initiator/saml2/saml2-beans.xml | 4 +-
.../idp/flows/sp/initiator/saml2/saml2-flow.xml | 2 +-
.../shibboleth/sp/flows/AbstractSPFlowTest.java | 6 +-
.../flows/saml2/SAML2SessionInitiatorFlowTest.java | 45 ++++-
.../saml2/profile/SAML2InitiatorConstants.java | 17 +-
.../config/BrowserSSOProfileConfiguration.java | 16 +-
.../sp/saml/saml2/profile/package-info.java | 18 ++
.../impl/BrowserSSOProfileConfiguration.java | 34 +++-
.../saml/saml2/profile/impl/AddAuthnRequest.java | 203 +++++++++++++++------
.../shibboleth/sp/profile/InitiatorConstants.java | 11 +-
...t.java => ValidateSessionInitiatorRequest.java} | 45 +++--
...va => ValidateSessionInitiatorRequestTest.java} | 48 ++++-
12 files changed, 345 insertions(+), 104 deletions(-)
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-beans.xml
index 91acb0e..2caa461 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-beans.xml
@@ -7,8 +7,8 @@
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-init-method="initialize" default-destroy-method="destroy">
- <bean id="InitializeRelyingPartyContext"
- class="net.shibboleth.sp.profile.impl.InitializeRelyingPartyContext" scope="prototype" />
+ <bean id="ValidateSessionInitiatorRequest"
+ class="net.shibboleth.sp.profile.impl.ValidateSessionInitiatorRequest" scope="prototype" />
<bean id="PrepareInboundMessageContext"
class="net.shibboleth.idp.saml.session.impl.PrepareInboundMessageContext" scope="prototype"
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-flow.xml
index 12a2dcb..9a413e9 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/saml2/saml2-flow.xml
@@ -4,7 +4,7 @@
parent="sp/initiator">
<action-state id="SAML2SessionInitiator">
- <evaluate expression="InitializeRelyingPartyContext" />
+ <evaluate expression="ValidateSessionInitiatorRequest" />
<evaluate expression="PrepareInboundMessageContext" />
<evaluate expression="SAMLProtocolAndRole" />
<evaluate expression="SAMLMetadataLookup" />
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/AbstractSPFlowTest.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/AbstractSPFlowTest.java
index 8c7da46..3bd39f3 100644
--- a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/AbstractSPFlowTest.java
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/AbstractSPFlowTest.java
@@ -155,8 +155,10 @@ public abstract class AbstractSPFlowTest extends AbstractFlowTest {
request.setContent(body.getBytes());
}
- protected void setApplicationRequest(@Nonnull final String applicationId) throws IOException {
- final DDF input = new DDF(null).structure();
+ protected void setApplicationRequest(@Nonnull final String applicationId, @Nullable DDF input) throws IOException {
+ if (input == null || !input.isstruct()) {
+ input = new DDF(null).structure();
+ }
input.addmember(ResolveApplication.APPLICATION_ID).string(applicationId);
setRequest("POST", input);
}
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/saml2/SAML2SessionInitiatorFlowTest.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/saml2/SAML2SessionInitiatorFlowTest.java
index fc56c29..e230246 100644
--- a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/saml2/SAML2SessionInitiatorFlowTest.java
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/saml2/SAML2SessionInitiatorFlowTest.java
@@ -32,6 +32,7 @@ import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.sp.context.AgentRequestContext;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.flows.AbstractSPFlowTest;
+import net.shibboleth.sp.profile.InitiatorConstants;
/**
* Unit test for the SP session-initiator flow.
@@ -41,6 +42,9 @@ public class SAML2SessionInitiatorFlowTest extends AbstractSPFlowTest {
/** Flow ID. */
@Nonnull public static final String FLOW_ID = "sp/session-initiator";
+ /** Flow ID. */
+ @Nonnull public static final String RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/SAML2/POST";
+
protected SAML2SessionInitiatorFlowTest() {
super(FLOW_ID);
}
@@ -53,7 +57,10 @@ public class SAML2SessionInitiatorFlowTest extends AbstractSPFlowTest {
@Test
public void testUnverified() throws IOException {
setDefaultAuth();
- setApplicationRequest("no-metadata");
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
+ setApplicationRequest("no-metadata", input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -69,7 +76,10 @@ public class SAML2SessionInitiatorFlowTest extends AbstractSPFlowTest {
@Test
public void testProfileDisabled() throws IOException {
setDefaultAuth();
- setApplicationRequest("no-profile");
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
+ setApplicationRequest("no-profile", input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -85,7 +95,10 @@ public class SAML2SessionInitiatorFlowTest extends AbstractSPFlowTest {
@Test
public void testNoEndpoint() throws IOException {
setDefaultAuth();
- setApplicationRequest("no-endpoint");
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
+ setApplicationRequest("no-endpoint", input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -93,6 +106,23 @@ public class SAML2SessionInitiatorFlowTest extends AbstractSPFlowTest {
assertOutputMessageEvent(result, AuthnEventIds.NO_POTENTIAL_FLOW);
}
+ /**
+ * Test flow without proper input.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testNoResponseURL() throws IOException {
+ setDefaultAuth();
+
+ setApplicationRequest(APPLICATION_ID, null);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ assertOutputMessageEvent(result, AuthnEventIds.NO_POTENTIAL_FLOW);
+ }
+
/**
* Test simple success case.
*
@@ -102,7 +132,10 @@ public class SAML2SessionInitiatorFlowTest extends AbstractSPFlowTest {
@Test
public void testSimple() throws IOException, MessageDecodingException {
setDefaultAuth();
- setApplicationRequest(APPLICATION_ID);
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
+ setApplicationRequest(APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -110,6 +143,7 @@ public class SAML2SessionInitiatorFlowTest extends AbstractSPFlowTest {
assertOutputMessageEvent(result, null);
final AuthnRequest req = validateOutputMessage(result);
+ Assert.assertFalse(req.isSigned());
Assert.assertNull(req.getSubject());
Assert.assertNull(req.getRequestedAuthnContext());
Assert.assertNull(req.getScoping());
@@ -155,7 +189,8 @@ public class SAML2SessionInitiatorFlowTest extends AbstractSPFlowTest {
assert authnRequest != null;
Assert.assertNotNull(authnRequest.getID());
Assert.assertTrue(Instant.now().isAfter(authnRequest.getIssueInstant()));
- Assert.assertFalse(authnRequest.isSigned());
+
+ Assert.assertEquals(authnRequest.getAssertionConsumerServiceURL(), RESPONSE_URL);
final NameIDPolicy pol = authnRequest.getNameIDPolicy();
assert pol != null;
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/InitiatorConstants.java b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/SAML2InitiatorConstants.java
similarity index 61%
copy from sp-server-api/src/main/java/net/shibboleth/sp/profile/InitiatorConstants.java
copy to sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/SAML2InitiatorConstants.java
index 5f49c0d..7e74962 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/profile/InitiatorConstants.java
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/SAML2InitiatorConstants.java
@@ -12,7 +12,7 @@
* limitations under the License.
*/
-package net.shibboleth.sp.profile;
+package net.shibboleth.sp.saml.saml2.profile;
import javax.annotation.Nonnull;
@@ -21,17 +21,10 @@ import net.shibboleth.shared.annotation.constraint.NotEmpty;
/**
* Constants for session initiator operations.
*/
-public final class InitiatorConstants {
-
- /** Authority input parameter. */
- @Nonnull @NotEmpty public static final String AUTHORITY = "authority";
-
- /** Target input parameter. */
- @Nonnull @NotEmpty public static final String TARGET = "target";
-
- /** Event indicating no authority identified. */
- @Nonnull @NotEmpty public static final String UNKNOWN_AUTHORITY = "UnknownAuthority";
+public final class SAML2InitiatorConstants {
/** Private constructor. */
- private InitiatorConstants() {}
+ private SAML2InitiatorConstants() {
+
+ }
}
\ No newline at end of file
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 210fa9c..391a354 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
@@ -25,7 +25,9 @@ import net.shibboleth.shared.annotation.constraint.NotLive;
import net.shibboleth.shared.annotation.constraint.Unmodifiable;
import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.core.AuthnContextClassRef;
+import org.opensaml.saml.saml2.core.AuthnRequest;
/** Configuration support for SP SAML 2.0 Browser SSO. */
public interface BrowserSSOProfileConfiguration extends SAMLArtifactConsumerProfileConfiguration,
@@ -49,5 +51,17 @@ public interface BrowserSSOProfileConfiguration extends SAMLArtifactConsumerProf
* @return required format
*/
@Nullable String getNameIDFormat(@Nullable final ProfileRequestContext profileRequestContext);
-
+
+
+ /**
+ * Get the SAML binding to insert into an {@link AuthnRequest} to control the response binding.
+ *
+ * <p>Defaults tp {@link SAMLConstants#SAML2_POST_BINDING_URI}.</p>
+ *
+ * @param profileRequestContext current profile request context
+ *
+ * @return response binding
+ */
+ @Nullable String getResponseBinding(@Nullable final ProfileRequestContext profileRequestContext);
+
}
\ No newline at end of file
diff --git a/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/package-info.java b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/package-info.java
new file mode 100644
index 0000000..c5fff8b
--- /dev/null
+++ b/sp-saml-api/src/main/java/net/shibboleth/sp/saml/saml2/profile/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * API classes for SAML 2.0 profiles.
+ */
+package net.shibboleth.sp.saml.saml2.profile;
\ No newline at end of file
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 b8a816c..31fed31 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
@@ -35,8 +35,10 @@ import net.shibboleth.shared.logic.FunctionSupport;
import net.shibboleth.shared.logic.PredicateSupport;
import org.opensaml.profile.context.ProfileRequestContext;
+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.SubjectLocality;
import org.opensaml.saml.saml2.metadata.RequestedAttribute;
@@ -74,6 +76,9 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2AssertionConsum
/** Lookup function to supply RequestedAttributes. */
@Nonnull private Function<ProfileRequestContext,Collection<RequestedAttribute>> requestedAttributesLookupStrategy;
+ /** Lookup function for response binding. */
+ @Nonnull private Function<ProfileRequestContext,String> responseBindingLookupStrategy;
+
/** Constructor. */
public BrowserSSOProfileConfiguration() {
this(PROFILE_ID);
@@ -97,6 +102,7 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2AssertionConsum
nameQualifierLookupStrategy = FunctionSupport.constant(null);
attributeIndexLookupStrategy = FunctionSupport.constant(null);
requestedAttributesLookupStrategy = FunctionSupport.constant(null);
+ responseBindingLookupStrategy = FunctionSupport.constant(SAMLConstants.SAML2_POST_BINDING_URI);
}
/** {@inheritDoc} */
@@ -379,5 +385,31 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2AssertionConsum
@Nonnull final Function<ProfileRequestContext,Collection<RequestedAttribute>> strategy) {
requestedAttributesLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
}
-
+
+ /** {@inheritDoc} */
+ @Nullable public String getResponseBinding(@Nullable ProfileRequestContext profileRequestContext) {
+ return responseBindingLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set the response binding to place into the {@link AuthnRequest}.
+ *
+ * <p>Defaults tp {@link SAMLConstants#SAML2_POST_BINDING_URI}.</p>
+ *
+ * @param binding response binding
+ */
+ public void setResponseBinding(@Nullable final String binding) {
+ responseBindingLookupStrategy = FunctionSupport.constant(binding);
+ }
+
+ /**
+ * Set a lookup strategy for
+ *
+ * @param strategy
+ */
+ public void setResponseBindingLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ responseBindingLookupStrategy =
+ Constraint.isNotNull(strategy, "Response binding lookup strategy cannot be null");
+ }
+
}
\ No newline at end of file
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddAuthnRequest.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddAuthnRequest.java
index d053eec..c7b7c96 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddAuthnRequest.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/AddAuthnRequest.java
@@ -18,6 +18,7 @@ import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
+import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -29,7 +30,9 @@ import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.security.IdentifierGenerationStrategy;
+import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.profile.AbstractApplicationAction;
+import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.saml.saml2.profile.config.BrowserSSOProfileConfiguration;
import org.opensaml.core.xml.XMLObjectBuilderFactory;
@@ -57,6 +60,7 @@ import org.opensaml.saml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml.saml2.core.RequesterID;
import org.opensaml.saml.saml2.core.Scoping;
import org.opensaml.saml.saml2.core.Subject;
+import org.opensaml.saml.saml2.metadata.NameIDFormat;
import org.opensaml.saml.saml2.metadata.RequestedAttribute;
import org.slf4j.Logger;
import net.shibboleth.shared.primitive.LoggerFactory;
@@ -73,6 +77,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
* on what the agent can override/supply.</p>
*
* @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MESSAGE}
* @event {@link EventIds#INVALID_MSG_CTX}
* @event {@link EventIds#INVALID_PROFILE_CTX}
* @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
@@ -102,6 +107,9 @@ public class AddAuthnRequest extends AbstractApplicationAction {
/** Applicable profile configuration. */
@NonnullBeforeExec private BrowserSSOProfileConfiguration profileConfiguration;
+ /** Input message from agent. */
+ @NonnullBeforeExec private DDF input;
+
/** EntityID to populate into Issuer element. */
@Nullable private String issuerId;
@@ -162,6 +170,13 @@ public class AddAuthnRequest extends AbstractApplicationAction {
return false;
}
+ input = ensureAgentRequestContext().getInput();
+ if (input == null) {
+ log.error("{} No input message", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return false;
+ }
+
final RelyingPartyContext rpCtx = profileRequestContext.getSubcontext(RelyingPartyContext.class);
if (rpCtx != null && rpCtx.getProfileConfig() instanceof BrowserSSOProfileConfiguration sso) {
profileConfiguration = sso;
@@ -217,14 +232,11 @@ public class AddAuthnRequest extends AbstractApplicationAction {
object.setIssueInstant(Instant.now());
object.setVersion(SAMLVersion.VERSION_20);
- // TODO: agent override
- final Integer index = profileConfiguration.getAttributeIndex(profileRequestContext);
- if (index != null) {
- log.debug("{} Setting AttributeConsumingServiceIndex to '{}' for SAML AuthnRequest", getLogPrefix(),
- index);
- object.setAttributeConsumingServiceIndex(index);
- }
-
+ // Response URL always comes from agent.
+ object.setAssertionConsumerServiceURL(input.getmember(InitiatorConstants.RESPONSE_URL).string());
+ // Protocol binding always comes from config, may be null.
+ object.setProtocolBinding(profileConfiguration.getResponseBinding(profileRequestContext));
+
if (issuerId != null) {
log.debug("{} Setting Issuer to {}", getLogPrefix(), issuerId);
final SAMLObjectBuilder<Issuer> issuerBuilder =
@@ -235,58 +247,113 @@ public class AddAuthnRequest extends AbstractApplicationAction {
} else {
log.debug("{} No issuer value available, leaving Issuer unset", getLogPrefix());
}
-
- // TODO: agent override
- if (profileConfiguration.isForceAuthn(profileRequestContext)) {
- log.debug("{} Setting ForceAuthn for SAML AuthnRequest", getLogPrefix());
- object.setForceAuthn(true);
+
+ Integer index = input.getmember(AuthnRequest.ATTRIBUTE_CONSUMING_SERVICE_INDEX_ATTRIB_NAME).integer();
+ if (index == null) {
+ index = profileConfiguration.getAttributeIndex(profileRequestContext);
+ }
+ if (index != null) {
+ log.debug("{} Setting AttributeConsumingServiceIndex to '{}'", getLogPrefix(), index);
+ object.setAttributeConsumingServiceIndex(index);
}
- // TODO: Only set passive based on agent request.
- if (false) {
- log.debug("{} Setting IsPassive for SAML AuthnRequest", getLogPrefix());
+ setForceAuthn(profileRequestContext, object);
+
+ final Integer passive = input.getmember(AuthnRequest.IS_PASSIVE_ATTRIB_NAME).integer();
+ if (passive != null && passive == 1) {
+ log.debug("{} Setting IsPassive", getLogPrefix());
object.setIsPassive(true);
}
- // TODO: agent override
- final NameIDPolicy nip = nipBuilder.buildObject();
+ object.setNameIDPolicy(buildNameIDPolicy(profileRequestContext, nipBuilder));
+ object.setRequestedAuthnContext(buildRequestedAuthnContext(profileRequestContext));
+ object.setSubject(buildSubject(profileRequestContext));
+ object.setScoping(buildScoping(profileRequestContext));
+ object.setExtensions(buildExtensions(profileRequestContext));
+
+ final MessageContext omc = profileRequestContext.getOutboundMessageContext();
+ assert omc != null;
+ omc.setMessage(object);
+
+ log.info("{} Generated AuthnRequest with ID {} from {}", getLogPrefix(), object.getID(), issuerId);
+ }
+
+ /**
+ * Set ForceAuthn appropriately.
+ *
+ * @param profileRequestContext profile request context
+ * @param authnRequest request object
+ */
+ private void setForceAuthn(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthnRequest authnRequest) {
+
+ final Integer forceAuthn = input.getmember(AuthnRequest.FORCE_AUTHN_ATTRIB_NAME).integer();
+ if (forceAuthn != null) {
+ if (profileConfiguration.isFeatureDisallowed(profileRequestContext,
+ BrowserSSOProfileConfiguration.FEATURE_FORCEAUTHN)) {
+ log.warn("{} Agent disallowed from overriding ForceAuthn", getLogPrefix());
+ } else if (forceAuthn == 1) {
+ log.debug("{} Setting ForceAuthn", getLogPrefix());
+ authnRequest.setForceAuthn(true);
+ return;
+ }
+ }
+
+ if (profileConfiguration.isForceAuthn(profileRequestContext)) {
+ log.debug("{} Setting ForceAuthn", getLogPrefix());
+ authnRequest.setForceAuthn(true);
+ }
+ }
+
+ /**
+ * Build {@link NameIDPolicy} object for request.
+ *
+ * @param profileRequestContext profile request context
+ * @param builder object builder
+ *
+ * @return policy object
+ */
+ @Nonnull NameIDPolicy buildNameIDPolicy(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final SAMLObjectBuilder<NameIDPolicy> builder) {
+
+ final NameIDPolicy nip = builder.buildObject();
nip.setAllowCreate(true);
- final String qualifier = profileConfiguration.getSPNameQualifier(profileRequestContext);
- if (qualifier != null) {
- log.debug("{} Setting NameIDPolicy SPNameQualifier to '{}' for SAML AuthnRequest", getLogPrefix(),
- qualifier);
- nip.setSPNameQualifier(qualifier);
+
+ String format = input.getmember(NameIDFormat.DEFAULT_ELEMENT_LOCAL_NAME).string();
+ if (format != null && profileConfiguration.isFeatureDisallowed(profileRequestContext,
+ BrowserSSOProfileConfiguration.FEATURE_NAMEIDFORMAT)) {
+ log.warn("{} Agent disallowed from overriding NameIDPolicy Format", getLogPrefix());
+ format = null;
+ }
+
+ if (format == null) {
+ format = profileConfiguration.getNameIDFormat(profileRequestContext);
}
- // TODO: agent override, use metadata for NameID Formats too?
- final String format = profileConfiguration.getNameIDFormat(profileRequestContext);
if (format != null) {
- log.debug("{} Setting NameIDPolicy Format to '{}' for SAML AuthnRequest", getLogPrefix(), format);
+ log.debug("{} Setting NameIDPolicy Format to '{}'", getLogPrefix(), format);
nip.setFormat(format);
}
-
- object.setNameIDPolicy(nip);
-
- final RequestedAuthnContext rac = buildRequestedAuthnContext(profileRequestContext);
- if (rac != null) {
- final AuthnContextComparisonTypeEnumeration operator =
- profileConfiguration.getAuthnContextComparison(profileRequestContext);
- if (operator != null) {
- log.debug("{} Setting RequestedAuthnContext comparison to {}", getLogPrefix(), operator);
- rac.setComparison(operator);
+
+ String qualifier = input.getmember(NameIDPolicy.SP_NAME_QUALIFIER_ATTRIB_NAME).string();
+ if (qualifier != null) {
+ if (profileConfiguration.isFeatureDisallowed(profileRequestContext,
+ BrowserSSOProfileConfiguration.FEATURE_SPNAMEQUALIFIER)) {
+ log.warn("{} Agent disallowed from overriding NameIDPolicy SPNameQualifier", getLogPrefix());
}
- object.setRequestedAuthnContext(rac);
+ qualifier = null;
}
-
- object.setSubject(buildSubject(profileRequestContext));
- object.setScoping(buildScoping(profileRequestContext));
- object.setExtensions(buildExtensions(profileRequestContext));
- final MessageContext omc = profileRequestContext.getOutboundMessageContext();
- assert omc != null;
- omc.setMessage(object);
+ if (qualifier == null) {
+ qualifier = profileConfiguration.getSPNameQualifier(profileRequestContext);
+ }
- log.info("{} Generated AuthnRequest with ID {} from {}", getLogPrefix(), object.getID(), issuerId);
+ if (qualifier != null) {
+ log.debug("{} Setting NameIDPolicy SPNameQualifier to '{}'", getLogPrefix(), qualifier);
+ nip.setSPNameQualifier(qualifier);
+ }
+
+ return nip;
}
/**
@@ -299,14 +366,25 @@ public class AddAuthnRequest extends AbstractApplicationAction {
@Nullable private RequestedAuthnContext buildRequestedAuthnContext(
@Nullable final ProfileRequestContext profileRequestContext) {
- // RequestedAuthnContext also based on profile configuration.
- assert profileConfiguration!=null;
- final List<String> classrefs = profileConfiguration.getAuthnContextClassRefs(profileRequestContext);
+ List<String> classrefs = input.getmember(AuthnContextClassRef.DEFAULT_ELEMENT_LOCAL_NAME).list().asList()
+ .stream()
+ .map(DDF::string)
+ .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableList())).get();
+ if (!classrefs.isEmpty() && profileConfiguration.isFeatureDisallowed(profileRequestContext,
+ BrowserSSOProfileConfiguration.FEATURE_AUTHNCONTEXT)) {
+ log.warn("{} Agent disallowed from overriding RequestedAuthnContext", getLogPrefix());
+ classrefs = CollectionSupport.emptyList();
+ }
+
+ if (classrefs.isEmpty()) {
+ profileConfiguration.getAuthnContextClassRefs(profileRequestContext);
+ }
+
if (classrefs.isEmpty()) {
return null;
}
- log.debug("{} Setting RequestedAuthnContext class refs to {}", getLogPrefix(), classrefs);
+ log.debug("{} Setting requested AuthnContextClassRef(s) {}", getLogPrefix(), classrefs);
final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
@@ -326,6 +404,29 @@ public class AddAuthnRequest extends AbstractApplicationAction {
rac.getAuthnContextClassRefs().add(obj);
});
+ String opstring = input.getmember("AuthnContextComparison").string();
+ if (opstring != null && profileConfiguration.isFeatureDisallowed(profileRequestContext,
+ BrowserSSOProfileConfiguration.FEATURE_AUTHNCONTEXT)) {
+ log.warn("{} Agent disallowed from overriding RequestedAuthnContext operator", getLogPrefix());
+ opstring = null;
+ }
+
+ final AuthnContextComparisonTypeEnumeration operator;
+ if (opstring != null) {
+ try {
+ operator = AuthnContextComparisonTypeEnumeration.valueOf(opstring);
+ } catch (final IllegalArgumentException e) {
+ log.warn("{} Error translating RequestedAuthnContext operator string", getLogPrefix(), e);
+ return null;
+ }
+ } else {
+ operator = profileConfiguration.getAuthnContextComparison(profileRequestContext);
+ }
+ if (operator != null) {
+ log.debug("{} Setting RequestedAuthnContext operator to {}", getLogPrefix(), operator);
+ rac.setComparison(operator);
+ }
+
return rac;
}
// Checkstyle: MethodLength ON
@@ -428,9 +529,7 @@ public class AddAuthnRequest extends AbstractApplicationAction {
* @return extension or null
*/
@Nullable private Extensions buildExtensions(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- // TODO: agent override?
-
+
assert profileConfiguration!=null;
final Collection<RequestedAttribute> attrs = profileConfiguration.getRequestedAttributes(profileRequestContext);
if (!attrs.isEmpty()) {
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/InitiatorConstants.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/InitiatorConstants.java
index 5f49c0d..2385923 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/profile/InitiatorConstants.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/InitiatorConstants.java
@@ -29,9 +29,14 @@ public final class InitiatorConstants {
/** Target input parameter. */
@Nonnull @NotEmpty public static final String TARGET = "target";
- /** Event indicating no authority identified. */
- @Nonnull @NotEmpty public static final String UNKNOWN_AUTHORITY = "UnknownAuthority";
+ /** State input parameter. */
+ @Nonnull @NotEmpty public static final String STATE = "state";
+
+ /** Response URL input parameter. */
+ @Nonnull @NotEmpty public static final String RESPONSE_URL = "response_url";
/** Private constructor. */
- private InitiatorConstants() {}
+ private InitiatorConstants() {
+
+ }
}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContext.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/ValidateSessionInitiatorRequest.java
similarity index 78%
rename from sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContext.java
rename to sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/ValidateSessionInitiatorRequest.java
index 878fd38..03d92f2 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContext.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/ValidateSessionInitiatorRequest.java
@@ -29,32 +29,33 @@ import net.shibboleth.sp.Application;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.profile.AbstractApplicationAction;
import net.shibboleth.sp.profile.InitiatorConstants;
-import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.profile.context.RelyingPartyContext;
import net.shibboleth.shared.logic.Constraint;
/**
- * Action that creates a {@link RelyingPartyContext} based on the identity of the eventual authenticating
- * authority (the IdP/OP/etc.).
+ * Initiating action of a typical Session Initiator flow, this validates the input message for
+ * correctness, and then creates a {@link RelyingPartyContext} based on the identity of the
+ * eventual authenticating authority (the IdP/OP/etc.).
*
* <p>The authority ID is potentially supplied by the agent in the request, or may be defaulted
* based on the {@link Application}.</p>
*
* <p>In rare cases (mainly SAML ECP), it is not strictly required that the ID be known in advance,
* so this is confgurable to allow session initiator flows to indicate via wiring how to handle
- * this case. If required but absent, the action will return the {@link AuthnEventIds#RESELECT_FLOW}
- * event.</p>
+ * this case.</p>
+ *
+ * <p>The input message is also validated to ensure any other required inputs are available.</p>
*
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#INVALID_MESSAGE}
* @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
- * @event {@link AuthnEventIds#RESELECT_FLOW}
*/
-public class InitializeRelyingPartyContext extends AbstractApplicationAction {
+public class ValidateSessionInitiatorRequest extends AbstractApplicationAction {
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(InitializeRelyingPartyContext.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateSessionInitiatorRequest.class);
/** Creation strategy for {@link RelyingPartyContext}. */
@Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextCreationStrategy;
@@ -66,7 +67,7 @@ public class InitializeRelyingPartyContext extends AbstractApplicationAction {
@Nullable private String relyingPartyId;
/** Constructor. */
- public InitializeRelyingPartyContext() {
+ public ValidateSessionInitiatorRequest() {
relyingPartyContextCreationStrategy = new ChildContextLookup<>(RelyingPartyContext.class, true);
requireRelyingPartyId = true;
}
@@ -107,25 +108,37 @@ public class InitializeRelyingPartyContext extends AbstractApplicationAction {
}
final DDF input = ensureAgentRequestContext().getInput();
- relyingPartyId = input != null ? input.getmember(InitiatorConstants.AUTHORITY).string() : null;
+ if (input == null) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ log.error("{} No input message from agent", getLogPrefix());
+ return false;
+ }
+
+ if (!input.getmember(InitiatorConstants.RESPONSE_URL).isstring()) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ log.error("{} Missing required input: {}", getLogPrefix(), InitiatorConstants.RESPONSE_URL);
+ return false;
+ }
+
+ relyingPartyId = input.getmember(InitiatorConstants.AUTHORITY).string();
if (relyingPartyId == null) {
log.debug("{} Request did not specify authority to use, falling back to Application default",
getLogPrefix());
relyingPartyId = ensureApplication().getAuthenticatingAuthority(profileRequestContext);
}
+ if (relyingPartyId == null && requireRelyingPartyId) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ log.error("{} Authenticating authority unknown", getLogPrefix());
+ return false;
+ }
+
return true;
}
/** {@inheritDoc} */
@Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- if (relyingPartyId == null && requireRelyingPartyId) {
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.RESELECT_FLOW);
- log.error("{} Authenticating authority unknown", getLogPrefix());
- return;
- }
-
final RelyingPartyContext rpContext = relyingPartyContextCreationStrategy.apply(profileRequestContext);
if (rpContext == null) {
ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContextTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/ValidateSessionInitiatorRequestTest.java
similarity index 67%
rename from sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContextTest.java
rename to sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/ValidateSessionInitiatorRequestTest.java
index f2e667c..a545c97 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/InitializeRelyingPartyContextTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/ValidateSessionInitiatorRequestTest.java
@@ -16,13 +16,13 @@ package net.shibboleth.sp.profile.impl;
import javax.annotation.Nonnull;
+import org.opensaml.profile.action.EventIds;
import org.springframework.webflow.execution.Event;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.profile.context.RelyingPartyContext;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
@@ -31,15 +31,15 @@ import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.profile.InitiatorConstants;
/**
- * Unit test for {@link InitializeRelyingPartyContext} action.
+ * Unit test for {@link ValidateSessionInitiatorRequest} action.
*/
@SuppressWarnings("javadoc")
-public class InitializeRelyingPartyContextTest extends BaseAgplicationActionTest {
+public class ValidateSessionInitiatorRequestTest extends BaseAgplicationActionTest {
/** Test value. */
@Nonnull @NotEmpty private final static String TEST_VALUE = "https://idp.example.org";
- private InitializeRelyingPartyContext action;
+ private ValidateSessionInitiatorRequest action;
/**
* Set up test.
@@ -52,7 +52,7 @@ public class InitializeRelyingPartyContextTest extends BaseAgplicationActionTest
prc.removeSubcontext(RelyingPartyContext.class);
- action = new InitializeRelyingPartyContext();
+ action = new ValidateSessionInitiatorRequest();
action.initialize();
}
@@ -63,20 +63,45 @@ public class InitializeRelyingPartyContextTest extends BaseAgplicationActionTest
public void tearDown() {
action.destroy();
}
+
+ @Test
+ public void testNoInput() {
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
+ Assert.assertNull(prc.getSubcontext(RelyingPartyContext.class));
+ }
+
+ @Test
+ public void testNoResponseURL() {
+ final DDF input = new DDF(null).structure();
+ arc.setInput(input);
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
+ Assert.assertNull(prc.getSubcontext(RelyingPartyContext.class));
+ }
@Test
public void testNoAuthorityWhenRequired() {
+ final DDF input = new DDF(null).structure();
+ input.addmember(InitiatorConstants.RESPONSE_URL).string("foo");
+ arc.setInput(input);
+
final Event event = action.execute(src);
- ActionTestingSupport.assertEvent(event, AuthnEventIds.RESELECT_FLOW);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
Assert.assertNull(prc.getSubcontext(RelyingPartyContext.class));
}
@Test
public void testNoAuthorityWhenNotRequired() throws ComponentInitializationException {
- action = new InitializeRelyingPartyContext();
+ action = new ValidateSessionInitiatorRequest();
action.setRequireRelyingPartyId(false);
action.initialize();
+ final DDF input = new DDF(null).structure();
+ input.addmember(InitiatorConstants.RESPONSE_URL).string("foo");
+ arc.setInput(input);
+
final Event event = action.execute(src);
ActionTestingSupport.assertProceedEvent(event);
@@ -88,7 +113,11 @@ public class InitializeRelyingPartyContextTest extends BaseAgplicationActionTest
@Test
public void testFallbackAuthority() {
application.setAuthenticatingAuthority(TEST_VALUE);
-
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(InitiatorConstants.RESPONSE_URL).string("foo");
+ arc.setInput(input);
+
final Event event = action.execute(src);
ActionTestingSupport.assertProceedEvent(event);
@@ -100,7 +129,8 @@ public class InitializeRelyingPartyContextTest extends BaseAgplicationActionTest
@Test
public void testInputAuthority() {
application.setAuthenticatingAuthority(TEST_VALUE);
- final DDF input = new DDF(null).structure();
+ final DDF input = new DDF(null).structure();
+ input.addmember(InitiatorConstants.RESPONSE_URL).string("foo");
input.addmember(InitiatorConstants.AUTHORITY).string(TEST_VALUE + "/shibboleth");
arc.setInput(input);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list