[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