[java-plugin-shibd-oidc] branch main updated: Start to stub out query and form_post token consumer flows

Phil Smart philip.smart at jisc.ac.uk
Fri Oct 17 13:29:06 UTC 2025


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

philsmart pushed a commit to branch main
in repository java-plugin-shibd-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-plugin-shibd-oidc.git;a=commit;h=4e4dc5138dfe4c4c7bee0e1989e62c68589bee40

The following commit(s) were added to refs/heads/main by this push:
     new 4e4dc51  Start to stub out query and form_post token consumer flows
4e4dc51 is described below

commit 4e4dc5138dfe4c4c7bee0e1989e62c68589bee40
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Oct 17 14:29:03 2025 +0100

    Start to stub out query and form_post token consumer flows
    
     - Add basic flow test to help drive development
---
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  87 +++++-
 .../sp/consumer/oidc/code/post/post-beans.xml      |  40 +++
 .../flows/sp/consumer/oidc/code/post/post-flow.xml |   9 +
 .../sp/consumer/oidc/code/query/query-beans.xml    |  41 +++
 .../sp/consumer/oidc/code/query/query-flow.xml     |   9 +
 .../idp/flows/sp/consumer/oidc/oidc-beans.xml      |  12 +
 .../idp/flows/sp/consumer/oidc/oidc-flow.xml       |  54 ++++
 ...Test.java => OIDCSessionInitiatorFlowTest.java} |   4 +-
 .../sp/oidc/flows/OIDCTokenConsumerFlowTest.java   | 310 +++++++++++++++++++++
 .../shibboleth/idp/module/conf/sp/sp.properties    |   2 +-
 10 files changed, 558 insertions(+), 10 deletions(-)

diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 83b75b0..f3352ad 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -12,13 +12,87 @@
                            http://www.springframework.org/schema/integration/ip https://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd"
 
     default-init-method="initialize" default-destroy-method="destroy">
+
+    <!-- Token consumer flows for different OIDC authentication flows (response_types) -->
+
+    <bean p:id="sp/consumer/oidc/code/query" class="net.shibboleth.sp.profile.BasicTokenConsumerFlowDescriptor">
+        <property name="activationCondition">
+            <bean class="net.shibboleth.sp.profile.context.logic.HttpServletRequestPredicate"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier">
+                <property name="httpServletRequestValidator">
+                    <bean class="net.shibboleth.shared.servlet.impl.ChainingHttpServletRequestValidator">
+                        <property name="validators">
+                            <list>
+                                <bean
+                                    class="net.shibboleth.shared.servlet.impl.BasicHttpServletRequestMethodValidator"
+                                    p:allowedMethods="#{{ 'GET' }}" />
+                                <bean
+                                    class="net.shibboleth.shared.servlet.impl.BasicHttpServletRequestContentTypeValidator"
+                                    p:allowNullContentType="true" />
+                               <ref bean="shibboleth.HttpServletRequestValidator.BasicParams.OIDC.Code.Response"/>
+                            </list>
+                        </property>
+                    </bean>
+                </property>
+            </bean>
+        </property>
+    </bean>
     
+    <bean p:id="sp/consumer/oidc/code/post" class="net.shibboleth.sp.profile.BasicTokenConsumerFlowDescriptor">
+        <property name="activationCondition">
+            <bean class="net.shibboleth.sp.profile.context.logic.HttpServletRequestPredicate"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier">
+                <property name="httpServletRequestValidator">
+                    <bean class="net.shibboleth.shared.servlet.impl.ChainingHttpServletRequestValidator">
+                        <property name="validators">
+                            <list>
+                                <bean
+                                    class="net.shibboleth.shared.servlet.impl.BasicHttpServletRequestMethodValidator"
+                                    p:allowedMethods="#{{ 'POST' }}" />
+                                <bean
+                                    class="net.shibboleth.shared.servlet.impl.BasicHttpServletRequestContentTypeValidator"
+                                    p:allowedContentTypes="application/x-www-form-urlencoded"
+                                    p:allowNullContentType="false" />
+                               <ref bean="shibboleth.HttpServletRequestValidator.BasicParams.OIDC.Code.Response"/>
+                            </list>
+                        </property>
+                    </bean>
+                </property>
+            </bean>
+        </property>
+    </bean>
+
+
+    <bean id="shibboleth.HttpServletRequestValidator.BasicParams.OIDC.Code.Response"
+        class="net.shibboleth.shared.servlet.impl.BasicHttpServletRequestParametersValidator"
+        p:enforceAllowedParameters="%{idp.http.oidc.enforceAllowedParameters:false}">
+        <property name="allowedParameters">
+            <set>
+                <value>code</value>
+                <value>state</value>
+            </set>
+        </property>
+        <property name="requiredParameters">
+            <set>
+                <value>code</value>
+                <value>state</value>
+            </set>
+        </property>
+        <property name="uniqueParameters">
+            <set>
+                <value>code</value>
+                <value>state</value>
+            </set>
+        </property>
+    </bean>
+
+
     <!-- Functions use by the flow and global beans TODO: IS there a better place for these -->
 
     <bean id="shibboleth.ChildLookup.OIDCProviderMetadataContext"
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext) }" />
-        
+
     <bean id="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound" parent="shibboleth.Functions.Compose">
         <constructor-arg name="g">
             <ref bean="shibboleth.ChildLookup.OIDCProviderMetadataContext" />
@@ -27,10 +101,10 @@
             <ref bean="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound" />
         </constructor-arg>
     </bean>
-    
+
     <bean id="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound" parent="shibboleth.Functions.Compose"
         c:g-ref="shibboleth.ChildLookup.OIDCPeerEntityContext" c:f-ref="shibboleth.MessageContextLookup.Outbound" />
-        
+
     <bean id="shibboleth.ChildLookup.OIDCPeerEntityContext"
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext) }" />
@@ -55,10 +129,9 @@
         </constructor-arg>
     </bean>
 
-<!--     <util:list id="shibboleth.DefaultProviderMetadataResolverResources">
-        <value>conditional:%{idp.home}/conf/authn/oidc-metadata-providers.xml</value>
-    </util:list> -->
-    
+    <!-- <util:list id="shibboleth.DefaultProviderMetadataResolverResources"> <value>conditional:%{idp.home}/conf/authn/oidc-metadata-providers.xml</value> 
+        </util:list> -->
+
     <!-- Auto-append system config file to resource set. -->
     <bean id="ExtendedProviderMetadataResolverResources"
         class="net.shibboleth.shared.spring.factory.CombiningListFactoryBean"
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml
new file mode 100644
index 0000000..1fe6a9c
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+    default-init-method="initialize" default-destroy-method="destroy">
+
+ <!--    <alias alias="shibboleth.HttpServletRequestValidator" name="shibboleth.HttpServletRequestValidator.BasicParams.SAML2.Artifact" /> -->
+
+<!--     <bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
+            p:messageType="org.opensaml.saml.saml2.core.Response">
+        <constructor-arg>
+            <bean class="org.opensaml.saml.saml2.binding.decoding.impl.HTTPArtifactDecoder" scope="prototype"
+                p:checkDuringInit="false"
+                p:parserPool-ref="shibboleth.ParserPool"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
+                p:bindingDescriptor-ref="shibboleth.Binding.SAML2Artifact"
+                p:peerEntityRole="#{T(org.opensaml.saml.saml2.metadata.IDPSSODescriptor).DEFAULT_ELEMENT_NAME}"
+                p:roleDescriptorResolver-ref="shibboleth.RoleDescriptorResolver"
+                p:identifierGenerationStrategy-ref="shibboleth.DefaultIdentifierGenerationStrategy"
+                p:selfEntityIDResolver-ref="ArtifactSupport.SelfEntityIDResolver"
+                p:artifactEndpointResolver-ref="shibboleth.EndpointResolver"
+                p:SOAPClient-ref="shibboleth.SOAPClient.SAML"
+                p:SOAPPipelineName="SP.SAML2.ArtifactDecoder"
+                p:SOAPClientSecurityConfigurationProfileId="#{T(net.shibboleth.saml.saml2.profile.config.BrowserSSOProfileConfiguration).PROFILE_ID}"
+                />
+        </constructor-arg>
+    </bean> -->
+    
+    <bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
+            p:messageType="org.opensaml.saml.saml2.core.Response">
+        <constructor-arg>
+            <bean class="net.shibboleth.oidc.profile.decoding.impl.HTTPPostAuthnResponseDecoder" scope="prototype"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"/>
+        </constructor-arg>
+    </bean>
+
+</beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-flow.xml
new file mode 100644
index 0000000..9b50b14
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-flow.xml
@@ -0,0 +1,9 @@
+<flow xmlns="http://www.springframework.org/schema/webflow" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+    parent="sp/consumer/oidc">
+
+    <!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml" />
+
+</flow>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-beans.xml
new file mode 100644
index 0000000..c0f4471
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-beans.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+    default-init-method="initialize" default-destroy-method="destroy">
+
+ <!--    <alias alias="shibboleth.HttpServletRequestValidator" name="shibboleth.HttpServletRequestValidator.BasicParams.SAML2.Artifact" /> -->
+
+<!--     <bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
+            p:messageType="org.opensaml.saml.saml2.core.Response">
+        <constructor-arg>
+            <bean class="org.opensaml.saml.saml2.binding.decoding.impl.HTTPArtifactDecoder" scope="prototype"
+                p:checkDuringInit="false"
+                p:parserPool-ref="shibboleth.ParserPool"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
+                p:bindingDescriptor-ref="shibboleth.Binding.SAML2Artifact"
+                p:peerEntityRole="#{T(org.opensaml.saml.saml2.metadata.IDPSSODescriptor).DEFAULT_ELEMENT_NAME}"
+                p:roleDescriptorResolver-ref="shibboleth.RoleDescriptorResolver"
+                p:identifierGenerationStrategy-ref="shibboleth.DefaultIdentifierGenerationStrategy"
+                p:selfEntityIDResolver-ref="ArtifactSupport.SelfEntityIDResolver"
+                p:artifactEndpointResolver-ref="shibboleth.EndpointResolver"
+                p:SOAPClient-ref="shibboleth.SOAPClient.SAML"
+                p:SOAPPipelineName="SP.SAML2.ArtifactDecoder"
+                p:SOAPClientSecurityConfigurationProfileId="#{T(net.shibboleth.saml.saml2.profile.config.BrowserSSOProfileConfiguration).PROFILE_ID}"
+                />
+        </constructor-arg>
+    </bean> -->
+    
+    <bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
+            p:messageType="com.nimbusds.openid.connect.sdk.AuthenticationResponse">
+        <constructor-arg>
+            <bean class="net.shibboleth.oidc.profile.decoding.impl.HTTPRedirectAuthnResponseDecoder" scope="prototype"
+                p:checkDuringInit="false"
+                p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"/>
+        </constructor-arg>
+    </bean>
+
+</beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-flow.xml
new file mode 100644
index 0000000..0f0970c
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-flow.xml
@@ -0,0 +1,9 @@
+<flow xmlns="http://www.springframework.org/schema/webflow" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+    parent="sp/consumer/oidc">
+
+    <!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-beans.xml" />
+
+</flow>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
new file mode 100644
index 0000000..9b8de1b
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+    default-init-method="initialize" default-destroy-method="destroy">
+
+
+
+</beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml
new file mode 100644
index 0000000..0af6134
--- /dev/null
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml
@@ -0,0 +1,54 @@
+<flow xmlns="http://www.springframework.org/schema/webflow" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+    abstract="true" parent="sp/consumer">
+
+    <action-state id="DoProfileWork">
+<!--        <evaluate expression="PopulateMetricContext" />-->
+       <evaluate expression="DecodeMessage" />
+<!--        <evaluate expression="PostDecodePopulateAuditContext" />-->
+
+       <!--  <evaluate expression="MapStateTokenToResource" />
+        
+        <evaluate expression="CheckMessageVersion" />
+        <evaluate expression="HandleNoPassive" />
+        <evaluate expression="ProcessCorrelationCookie" />
+        <evaluate expression="SAMLProtocolAndRole" />
+        <evaluate expression="SAMLMetadataLookup" />
+
+        <evaluate expression="InitializeRelyingPartyContextFromSAMLPeer" />
+        <evaluate expression="SelectRelyingPartyConfiguration" /> -->
+<!--        <evaluate expression="PostLookupPopulateAuditContext" />-->
+       <!--  <evaluate expression="SelectProfileConfiguration" />
+
+        <evaluate expression="PopulateSignatureValidationParameters" />
+        <evaluate expression="PopulateClientTLSValidationParameters" />
+        <evaluate expression="PopulateInboundMessageContextWithSAMLSelf" />
+        
+        <evaluate expression="HandleResponse" />
+        <evaluate expression="PopulateDecryptionParameters" />
+        <evaluate expression="DecryptAssertions" />
+        <evaluate expression="ValidateAssertions" />
+        <evaluate expression="ProcessAssertionsForAuthentication" />
+        <evaluate expression="DecryptNameIDs" />
+        <evaluate expression="DecryptAttributes" />
+        <evaluate expression="ExtractSAMLAttributes" /> -->
+        <!-- <evaluate expression="PrepareAgentResponse" /> -->
+<!--        <evaluate expression="PostAssertionPopulateAuditContext" />-->
+<!--        <evaluate expression="PostResponsePopulateAuditContext" />-->
+        
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="proceed" />
+    </action-state>
+    
+    <global-transitions>
+        <!-- Remap some internal action errors. -->
+        <transition on="InvalidCredentials" to="InvalidMessage" />
+        <transition on="UnableToValidateAssertion" to="MessageProcessingError" />
+    </global-transitions>
+    
+    <!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml" />
+
+</flow>
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
similarity index 99%
rename from sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
rename to sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
index ee30e9a..86629a6 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
@@ -63,7 +63,7 @@ import net.shibboleth.sp.profile.impl.IssueCorrelationCookie;
                 }
         )
 @WebAppConfiguration
-public class OIDCAuthenticationFlowTest extends AbstractSPFlowTest {
+public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
     
     /** Flow ID. */
     @Nonnull public static final String FLOW_ID = "sp/session-initiator";
@@ -76,7 +76,7 @@ public class OIDCAuthenticationFlowTest extends AbstractSPFlowTest {
 
 
     /** Constructor. */
-    protected OIDCAuthenticationFlowTest() {
+    protected OIDCSessionInitiatorFlowTest() {
         super(FLOW_ID);
     }
     
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
new file mode 100644
index 0000000..9658e21
--- /dev/null
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.flows;
+
+import static org.testng.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.NameIDType;
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.xmlsec.SignatureSigningParameters;
+import org.opensaml.xmlsec.signature.SignableXMLObject;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.opensaml.xmlsec.signature.support.SignatureException;
+import org.opensaml.xmlsec.signature.support.SignatureSupport;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.codec.DecodingException;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy;
+import net.shibboleth.shared.xml.XMLParserException;
+import net.shibboleth.sp.context.AgentRequestContext;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.flows.AbstractSPFlowTest;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
+import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
+import net.shibboleth.sp.profile.ConsumerConstants;
+
+/**
+ * Unit test for the SP session-initiator flow.
+ */
+ at ContextConfiguration(
+        locations = {
+                "classpath:/net/shibboleth/sp/oidc-test-beans.xml", },
+        initializers = {
+                TestSPOIDCEnvironmentApplicationContextInitializer.class,
+                }
+        )
+ at WebAppConfiguration
+public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
+    
+    /** Flow ID. */
+    @Nonnull public static final String FLOW_ID = "sp/token-consumer";
+
+    /** Issuer. */
+    @Nonnull public static final String ISSUER = "https://op.example.org";
+
+    /** Audience. */
+    @Nonnull public static final String AUDIENCE = "https://testsp.example.org";
+
+    /** REDIRECT URI. */
+    @Nonnull public static final String RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/callback";
+
+    /** Resource URL. */
+    @Nonnull public static final String RESOURCE_URL = "https://sp.example.org/secure";
+
+    @Autowired
+    @Qualifier("shibboleth.SessionIDGenerator")
+    protected IdentifierGenerationStrategy idGenerator;
+    
+    /** Dummy signing key. */
+    @Autowired @Qualifier("dummy.idp.Credential") protected Credential idpCredential;
+
+    /** Constructor. */
+    public OIDCTokenConsumerFlowTest() {
+        super(FLOW_ID);
+    }
+    
+    /** Pre-test work. */
+    @BeforeMethod
+    public void beforeMethod() {
+        setDefaultAuth();
+    }
+
+    /**
+     * Test successful flow.
+     * 
+     * @throws IOException 
+     */
+    @Test
+    public void testSuccess() throws IOException {
+        final AuthenticationSuccessResponse response = 
+                buildOIDCAuthorizationCodeResponse(RESPONSE_URL, ResponseMode.QUERY, "state");
+
+        final DDF input = buildRemotedQueryStringResponse(response);
+        setApplicationRequest(APPLICATION_ID, input);
+
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        final DDF output = assertOutputMessageSuccess(result);
+        assert output != null;
+        System.out.println("testSuccess output: " + output.toString());
+//        validateOutputMessage(result, CollectionSupport.singleton("mail"),
+//                RESOURCE_URL, response.getAssertions().get(0).getAuthnStatements().get(0).getSessionIndex());
+    }
+
+    /**
+     * Decode an encoded response and run sanity checks against it.
+     * 
+     * @param result flow execution result
+     * @param attributeIds set of attribute IDs to check for
+     * @param resource resource URL used in final redirect
+     * @param sessionIndex SessionIndex from assertion
+     * 
+     * @return the output object
+     * 
+     * TODO from the SAML variant. 
+     */
+    @Nonnull private DDF validateOutputMessage(@Nonnull final FlowExecutionResult result,
+            @Nullable final Set<String> attributeIds, @Nullable final String resource, @Nullable final String sessionIndex) {
+        final ProfileRequestContext prc = retrieveProfileRequestContext(result);
+        assert prc != null;
+        final AgentRequestContext arc = prc.ensureSubcontext(AgentRequestContext.class);
+        final DDF output = arc.getOutput();
+
+        assert output != null;
+        Assert.assertTrue(output.isstruct());
+        final DDF http = output.getmember(RemotedHttpServletRequest.STRUCTURE_NAME);
+        Assert.assertTrue(http.isstruct());
+        
+        final byte[] redirect = http.getmember(RemotedHttpServletResponse.REDIRECT).unsafe_string();
+        Assert.assertEquals(resource != null ? resource.getBytes(StandardCharsets.UTF_8) : null,  redirect);
+        
+        Assert.assertTrue(output.getmember(ConsumerConstants.VALIDATION_ERRORS).isnull());
+        
+        final Set<String> mutableIds = new HashSet<>(attributeIds);
+        for (final DDF attr : output.getmember(ConsumerConstants.SESSION_ATTRIBUTES).asList()) {
+            Assert.assertTrue(mutableIds.contains(attr.name()));
+            mutableIds.remove(attr.name());
+            if ("mail".equals(attr.name())) {
+                Assert.assertEquals(attr.asList().stream().map(DDF::string).toList(),
+                        CollectionSupport.singletonList("jdoe at example.org"));
+            } else if ("displayName".equals(attr.name())) {
+                Assert.assertEquals(attr.asList().stream().map(DDF::string).toList(),
+                        CollectionSupport.singletonList("John Doe"));
+            } else if ("eduPersonScopedAffiliation".equals(attr.name())) {
+                Assert.assertEquals(attr.asList().stream().map(ddf -> {return ddf.getmember("value").string();}).toList(),
+                        CollectionSupport.listOf("staff", "employee"));
+                Assert.assertEquals(attr.asList().stream().map(ddf -> {return ddf.getmember("scope").string();}).toList(),
+                        CollectionSupport.listOf("example.org", "example.org"));
+            }
+        }
+        Assert.assertTrue(mutableIds.isEmpty());
+        
+        try {
+            final String s = output.getmember(ConsumerConstants.SESSION_OPAQUE).string();
+            assert s != null;
+            final byte[] opaque = Base64Support.decodeURLSafe(s);
+            try (final ByteArrayInputStream in = new ByteArrayInputStream(opaque)) {
+                final XMLObject obj = XMLObjectSupport.unmarshallFromInputStream(parserPool, in);
+                if (obj instanceof final NameID nameID) {
+                    Assert.assertEquals(nameID.getValue(), "jdoe at example.org");
+                    Assert.assertEquals(nameID.getFormat(), NameIDType.EMAIL);
+                    Assert.assertEquals(nameID.getSPProvidedID(), sessionIndex);
+                } else {
+                    Assert.fail("Session data was not a NameID");
+                }
+            }
+        } catch (final DecodingException|IOException|UnmarshallingException|XMLParserException e) {
+            Assert.fail(e.getMessage());
+        }
+        
+        return output;
+    }
+    
+    /**
+     * Tests the output contains token validation error starting with the designated string.
+     * 
+     * @param output output message
+     * @param messagePrefix error message to check for
+     */
+    private void validateAssertionError(@Nullable final DDF output, @Nullable final String messagePrefix) {
+        if (output == null) {
+            Assert.fail("No output message");
+            return;
+        }
+        
+        final DDF errors = output.getmember(ConsumerConstants.VALIDATION_ERRORS);
+        Assert.assertTrue(errors.islist());
+        
+        final String msg = errors.asList().get(0).string();
+        Assert.assertTrue(msg != null && msg.startsWith(messagePrefix));
+    }
+    
+    /**
+     * Builds a dummy OIDC authorization code response.
+     * 
+     * @param redirectURI
+     *            redirect URI
+     * @param mode
+     *            response mode
+     * 
+     * @return authentication success response for the token consumer flow
+     */
+    private AuthenticationSuccessResponse buildOIDCAuthorizationCodeResponse(
+            @Nonnull final String redirect, @Nonnull final ResponseMode mode, final String stateString) {
+
+        final AuthorizationCode code = new AuthorizationCode("dummy-auth-code");
+        final State state = new State(stateString);
+        URI redirectURI = null;
+        try {
+            redirectURI = new URI(redirect);
+        } catch (final URISyntaxException e) {
+            fail("Bad redirect URI", e);
+        }
+
+        // Build a dummy success response (authorization code)
+        return new AuthenticationSuccessResponse(
+            redirectURI,
+            code, 
+            null, 
+            null,            
+            state, 
+            null,            
+            mode 
+        );
+    }
+    
+    
+    /**
+     * Encodes an OIDC response into the query string of a redirect URI embedded in a remoted message.
+     * 
+     * @param response succesful OIDC response to encode
+     * 
+     * @return input object suitable for token consumer flow
+     * 
+     * @throws IOException on error 
+     */
+    @Nonnull private DDF buildRemotedQueryStringResponse(@Nonnull final AuthenticationSuccessResponse response) 
+            throws IOException {                
+
+        final DDF obj = new DDF(null).structure();
+        obj.addmember(ConsumerConstants.BASE_URL).unsafe_string(RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
+        final DDF http = obj.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
+        
+        http.addmember(RemotedHttpServletRequest.METHOD).string("GET");
+        http.addmember(RemotedHttpServletRequest.REMOTE_ADDR).string("192.168.1.1");
+        http.addmember(RemotedHttpServletRequest.REQUEST_URL).unsafe_string(RESPONSE_URL.getBytes(StandardCharsets.UTF_8));
+        final URI responseURI = response.toURI();
+        http.addmember(RemotedHttpServletRequest.QUERY_STRING).string(Constraint
+                .isNotNull(responseURI.getQuery(), "Query string is null"));
+        return obj;
+    }
+    
+
+    /**
+     * Sign object.
+     * 
+     * @param signable object to sign
+     * 
+     * @throws IOException 
+     */
+    public void sign(@Nonnull final SignableXMLObject signable) throws IOException {
+
+        final SignatureSigningParameters signingParameters = new SignatureSigningParameters();
+        signingParameters.setSigningCredential(idpCredential);
+        signingParameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
+        signingParameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
+
+        try {
+            SignatureSupport.signObject(signable, signingParameters);
+        } catch (final SecurityException | MarshallingException | SignatureException e) {
+            throw new IOException(e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties
index ad27fe8..b66e8d7 100644
--- a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties
@@ -25,7 +25,7 @@ sp.service.agents.checkInterval = PT5M
 
 # Default precedence/set of Session Initiator and Token Consumer flows to attempt
 sp.application.sessionInitiators = oidc
-sp.application.tokenConsumers = saml2/artifact, saml2/post, saml2/post-simplesign
+sp.application.tokenConsumers = oidc/code/query, oidc/code/post
 
 # General SP cookie properties
 #sp.cookie.secure = true

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


More information about the commits mailing list