[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