[java-plugin-shibd] branch main updated: Merge state tracking redesign from dev branch.
Codeberg
noreply at shibboleth.net
Wed May 6 15:10:35 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-plugin-shibd.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd/commit/2683e8624d8d9ac8bb22c05b19367aef20959ee2
The following commit(s) were added to refs/heads/main by this push:
new 2683e86 Merge state tracking redesign from dev branch.
2683e86 is described below
commit 2683e8624d8d9ac8bb22c05b19367aef20959ee2
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Wed May 6 11:05:11 2026 -0400
Merge state tracking redesign from dev branch.
---
.../idp/flows/sp/initiator/disco/disco-beans.xml | 8 +
.../idp/flows/sp/initiator/disco/disco-flow.xml | 3 +
.../idp/flows/sp/initiator/initiator-beans.xml | 5 +-
.../sp/logout-initiator/logout-initiator-beans.xml | 6 -
.../sp/logout-initiator/logout-initiator-flow.xml | 1 -
.../flows/sp/logout/initiator/initiator-beans.xml | 11 --
.../session-initiator/session-initiator-beans.xml | 11 +-
.../session-initiator/session-initiator-flow.xml | 2 +-
.../sp/token-consumer/token-consumer-flow.xml | 4 -
.../shibboleth/idp/module/conf/sp/sp.properties | 7 +-
.../net/shibboleth/sp/conf/agents-system.xml | 21 +-
.../sp/flows/LogoutInitiatorFlowTest.java | 19 --
.../sp/flows/SessionInitiatorFlowTest.java | 72 +++++++
...SPEnvironmentApplicationContextInitializer.java | 3 +
.../main/java/net/shibboleth/sp/Application.java | 11 --
.../sp/context/TokenConsumerContext.java | 55 ------
.../sp/profile/AbstractStateTokenManager.java | 114 -----------
.../AbstractTokenConsumerResponseAction.java | 17 ++
.../shibboleth/sp/profile/ConsumerConstants.java | 6 +-
.../sp/profile/PreserveStateDataAction.java | 3 +-
.../net/shibboleth/sp/profile/SPConstants.java | 3 +
.../shibboleth/sp/profile/StateTokenManager.java | 73 -------
.../MessageCorrelationIDLookupFunction.java | 33 ----
.../shibboleth/sp/state/AbstractStateManager.java | 12 +-
.../net/shibboleth/sp/impl/BasicApplication.java | 36 +---
.../sp/impl/CookieStateTokenManager.java | 183 ------------------
.../sp/impl/PassthroughStateTokenManager.java | 70 -------
.../sp/impl/StorageServiceStateTokenManager.java | 139 --------------
.../sp/profile/impl/EncodeAgentResponse.java | 50 ++++-
.../sp/profile/impl/IssueCorrelationCookie.java | 199 -------------------
.../sp/profile/impl/IssueDiscoveryRequest.java | 44 +++--
.../sp/profile/impl/MapResourceToStateToken.java | 156 ---------------
.../sp/profile/impl/MapStateTokenToResource.java | 134 -------------
.../sp/profile/impl/PrepareAgentErrorResponse.java | 47 ++++-
.../impl/PrepareDiscoveryStateDataContext.java | 114 +++++++++++
.../sp/profile/impl/PreservePostData.java | 206 +++++++++++++++-----
.../sp/profile/impl/ProcessCorrelationCookie.java | 173 -----------------
.../sp/profile/impl/RecoverStateData.java | 80 ++++++--
.../sp/state/impl/CookieStateManager.java | 56 +++---
.../sp/state/impl/StorageServiceStateManager.java | 10 +-
.../impl/InitializeAgentRequestContextTest.java | 18 +-
.../sp/impl/CookieStateTokenManagerTest.java | 151 ---------------
.../sp/impl/PassthroughStateTokenManagerTest.java | 69 -------
.../impl/StorageServiceStateTokenManagerTest.java | 80 --------
.../sp/profile/impl/BaseAgentRequestTest.java | 6 -
.../profile/impl/IssueCorrelationCookieTest.java | 163 ----------------
.../sp/profile/impl/IssueDiscoveryRequestTest.java | 11 +-
.../profile/impl/MapResourceToStateTokenTest.java | 213 ---------------------
.../sp/profile/impl/PreservePostDataTest.java | 72 ++++++-
.../profile/impl/ProcessCorrelationCookieTest.java | 135 -------------
.../sp/state/impl/CookieStateManagerTest.java | 100 ++++++++--
.../state/impl/StorageServiceStateManagerTest.java | 100 ++++++++--
52 files changed, 873 insertions(+), 2442 deletions(-)
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-beans.xml
index 0cf4bb6..42cacb4 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-beans.xml
@@ -16,6 +16,14 @@
p:requireDiscoveryURL="true"
p:requireRelyingPartyId="false" />
+ <bean id="PrepareDiscoveryStateDataContext"
+ class="net.shibboleth.sp.profile.impl.PrepareDiscoveryStateDataContext" scope="prototype" />
+
+ <bean id="PreserveStateData"
+ class="net.shibboleth.sp.profile.PreserveStateDataAction" scope="prototype"
+ p:errorFatal="%{sp.stateToken.errorsFatal:false}"
+ p:createOutputObjects="true" />
+
<bean id="IssueDiscoveryRequest"
class="net.shibboleth.sp.profile.impl.IssueDiscoveryRequest" scope="prototype"
p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier"
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-flow.xml
index e3d0241..b857c4b 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-flow.xml
@@ -5,6 +5,9 @@
<action-state id="DiscoverySessionInitiator">
<evaluate expression="ValidateSessionInitiatorRequest" />
+ <evaluate expression="PrepareDiscoveryStateDataContext" />
+ <evaluate expression="PreserveStateData" />
+ <evaluate expression="PreservePostData" />
<evaluate expression="IssueDiscoveryRequest" />
<evaluate expression="'proceed'" />
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/initiator-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/initiator-beans.xml
index 85815b8..b2c3f71 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/initiator-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/initiator-beans.xml
@@ -7,15 +7,12 @@
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="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/session-initiator" />
- <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.initiator:SPAgent.SessionInitiator}" />
-
<bean id="PreservePostData"
class="net.shibboleth.sp.profile.impl.PreservePostData" scope="prototype"
p:errorFatal="%{sp.postData.errorsFatal:false}"
p:lifetime="%{sp.postData.lifetime:PT15M}"
p:storageService-ref="#{'%{sp.postData.StorageService:shibboleth.StorageService}'.trim()}"
p:cookieManager-ref="shibboleth.sp.RemotedCookieManager"
- p:cookiePrefix="#{'%{sp.postData.cookiePrefix:__Host-shibsp_post_}'.trim()}" />
+ p:cookiePrefix="#{'%{sp.postData.cookiePrefix:__Host-shibsp_post}'.trim()}" />
</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-beans.xml
index f197e34..330ceac 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-beans.xml
@@ -10,10 +10,4 @@
<bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/session-initiator" />
<bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.initiator:SPAgent.SessionInitiator}" />
- <bean id="MapResourceToStateToken"
- class="net.shibboleth.sp.profile.impl.MapResourceToStateToken" scope="prototype"
- p:createOutputObjects="true"
- p:targetRequired="false"
- p:errorFatal="%{sp.stateToken.errorsFatal:false}" />
-
</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-flow.xml
index b9da789..3cfff81 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-flow.xml
@@ -16,7 +16,6 @@
<on-entry>
<evaluate expression="new net.shibboleth.shared.collection.ReflectionSafeIterator(opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.sp.context.AgentRequestContext)).getApplication().getLogoutInitiators(opensamlProfileRequestContext).iterator())" result="flowScope.LogoutInitiatorIterator" />
</on-entry>
- <evaluate expression="MapResourceToStateToken" />
<evaluate expression="'proceed'" />
<!-- Branch to child flow for actual work. -->
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-beans.xml
index 85815b8..8aae2de 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-beans.xml
@@ -6,16 +6,5 @@
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">
-
- <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/session-initiator" />
- <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.initiator:SPAgent.SessionInitiator}" />
-
- <bean id="PreservePostData"
- class="net.shibboleth.sp.profile.impl.PreservePostData" scope="prototype"
- p:errorFatal="%{sp.postData.errorsFatal:false}"
- p:lifetime="%{sp.postData.lifetime:PT15M}"
- p:storageService-ref="#{'%{sp.postData.StorageService:shibboleth.StorageService}'.trim()}"
- p:cookieManager-ref="shibboleth.sp.RemotedCookieManager"
- p:cookiePrefix="#{'%{sp.postData.cookiePrefix:__Host-shibsp_post_}'.trim()}" />
</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml
index 6ce382b..9e01348 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-beans.xml
@@ -9,10 +9,9 @@
<bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/session-initiator" />
<bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.initiator:SPAgent.SessionInitiator}" />
-
- <bean id="MapResourceToStateToken"
- class="net.shibboleth.sp.profile.impl.MapResourceToStateToken" scope="prototype"
- p:createOutputObjects="true"
- p:errorFatal="%{sp.stateToken.errorsFatal:false}" />
-
+
+ <!-- Default configuration pulls state token from input message if present. -->
+ <bean id="RecoverStateData"
+ class="net.shibboleth.sp.profile.impl.RecoverStateData" scope="prototype" />
+
</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml
index 1cf23bb..5490cd9 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml
@@ -16,7 +16,7 @@
<on-entry>
<evaluate expression="new net.shibboleth.shared.collection.ReflectionSafeIterator(opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.sp.context.AgentRequestContext)).getApplication().getSessionInitiators(opensamlProfileRequestContext).iterator())" result="flowScope.SessionInitiatorIterator" />
</on-entry>
- <evaluate expression="MapResourceToStateToken" />
+ <evaluate expression="RecoverStateData" />
<evaluate expression="'proceed'" />
<!-- Branch to child flow for actual work. -->
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-flow.xml
index 8bff417..bb6d54f 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-flow.xml
@@ -24,10 +24,6 @@
<transition on="proceed" to="EncodeAgentResponse" />
</subflow-state>
- <action-state id="NoPotentialFlow">
- <evaluate expression="'NoPotentialFlow'" />
- </action-state>
-
<!-- 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/token-consumer/token-consumer-beans.xml" />
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
index cf09a9f..377f5e8 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
@@ -30,7 +30,7 @@ sp.service.agents.checkInterval = PT5M
# Controls storage back-end for storage-based state tokens
#sp.stateToken.StorageService = shibboleth.StorageService
# Cookie prefix when using cookie-backed state
-#sp.stateToken.cookiePrefix = __Host-shibsp_state_
+#sp.stateToken.cookiePrefix = __Host-shibsp_state
# Lifetime to preserve state before discarding
#sp.stateToken.lifetime = PT30M
# Whether to seal/encrypt data stored by state manager
@@ -38,14 +38,11 @@ sp.service.agents.checkInterval = PT5M
# Whether to check for replay of state tokens
#sp.stateToken.checkReplay = false
-# Request/response correlation control
-#sp.correlation.cookiePrefix = __Host-shibsp_req_
-
# POST data preservation controls
#sp.postData.preservation = false
#sp.postData.limit = 1048576
#sp.postData.lifetime = PT5M
-#sp.postData.cookiePrefix = __Host-shibsp_post_
+#sp.postData.cookiePrefix = __Host-shibsp_post
#sp.postData.StorageService = shibboleth.StorageService
#sp.postData.errorsFatal = false
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
index f24a6cc..590e1ed 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
@@ -63,28 +63,11 @@
p:unverifiedConfiguration-ref="DefaultUnverifiedRelyingPartyConfiguration"
p:relyingPartyConfigurations-ref="shibboleth.sp.RelyingPartyOverrides"
p:discoveryServiceLookupStrategy="#{getObject('%{sp.discoveryURLFunction:DefaultDiscoveryServiceStrategy}'.trim())}"
- p:stateTokenManager-ref="#{'%{sp.stateToken.Manager:shibboleth.sp.CookieStateTokenManager}'.trim()}"
- p:stateManager-ref="shibboleth.sp.CookieStateManager" />
- <!--
- // TODO: make this property-driven once we retire the older interface
-
p:stateManager-ref="#{'%{sp.stateToken.Manager:shibboleth.sp.CookieStateManager}'.trim()}" />
- -->
<bean id="shibboleth.sp.Application" class="net.shibboleth.sp.impl.BasicApplication" abstract="true"
p:metricName="net.shibboleth.sp.relyingparty.configurations" />
- <bean id="shibboleth.sp.PassthroughStateTokenManager" class="net.shibboleth.sp.impl.PassthroughStateTokenManager" lazy-init="true" />
-
- <!-- Deprecated, will be removed once we redo the flows. -->
-
- <bean id="shibboleth.sp.StorageStateTokenManager" class="net.shibboleth.sp.impl.StorageServiceStateTokenManager" lazy-init="true"
- p:storageService-ref="#{'%{sp.stateToken.StorageService:shibboleth.StorageService}'.trim()}" />
-
- <bean id="shibboleth.sp.CookieStateTokenManager" class="net.shibboleth.sp.impl.CookieStateTokenManager" lazy-init="true"
- p:cookieManager-ref="shibboleth.sp.RemotedCookieManager"
- p:cookiePrefix="#{'%{sp.stateToken.cookiePrefix:__Host-shibsp_state_}'.trim()}" />
-
<!-- State Managers -->
<bean id="shibboleth.sp.StateManager" abstract="true"
@@ -101,12 +84,12 @@
<bean id="shibboleth.sp.StorageAndCookieStateManager" parent="shibboleth.sp.StorageOnlyStateManager"
class="net.shibboleth.sp.state.impl.StorageServiceStateManager" lazy-init="true"
p:cookieManager-ref="shibboleth.sp.RemotedCookieManager"
- p:cookiePrefix="#{'%{sp.stateToken.cookiePrefix:__Host-shibsp_state_}'.trim()}" />
+ p:cookiePrefix="#{'%{sp.stateToken.cookiePrefix:__Host-shibsp_state}'.trim()}" />
<bean id="shibboleth.sp.CookieStateManager" parent="shibboleth.sp.StateManager"
class="net.shibboleth.sp.state.impl.CookieStateManager" lazy-init="true"
p:cookieManager-ref="shibboleth.sp.RemotedCookieManager"
- p:cookiePrefix="#{'%{sp.stateToken.cookiePrefix:__Host-shibsp_state_}'.trim()}" />
+ p:cookiePrefix="#{'%{sp.stateToken.cookiePrefix:__Host-shibsp_state}'.trim()}" />
<!-- More traditional beans akin to IdP service. -->
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutInitiatorFlowTest.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutInitiatorFlowTest.java
index a01df5c..5ca64af 100644
--- a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutInitiatorFlowTest.java
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutInitiatorFlowTest.java
@@ -92,23 +92,4 @@ public class LogoutInitiatorFlowTest extends AbstractSPFlowTest {
assertOutputMessageEvent(result, AuthnEventIds.NO_POTENTIAL_FLOW);
}
-
- /**
- * Test flow with no initiator subflows installed.
- * @throws IOException
- */
- @Test
- public void testNoFlowsNoTarget() throws IOException {
- setDefaultAuth();
-
- final DDF input = new DDF(null).structure();
- input.addmember(SPConstants.APPLICATION).string(APPLICATION_ID);
- setRequest("POST", input);
-
- final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
- assertFlowExecutionResult(result, FLOW_ID);
- assertFlowExecutionOutcome(result.getOutcome());
- assertOutputMessageEvent(result, AuthnEventIds.NO_POTENTIAL_FLOW);
- }
-
}
\ No newline at end of file
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SessionInitiatorFlowTest.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SessionInitiatorFlowTest.java
index 5ca67a4..37eaf3c 100644
--- a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SessionInitiatorFlowTest.java
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/SessionInitiatorFlowTest.java
@@ -21,11 +21,17 @@ import javax.annotation.Nonnull;
import org.opensaml.profile.action.EventIds;
import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
import org.testng.annotations.Test;
+import com.google.common.escape.Escaper;
+import com.google.common.net.UrlEscapers;
+
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
+import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
+import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
/**
@@ -35,6 +41,12 @@ public class SessionInitiatorFlowTest extends AbstractSPFlowTest {
/** Flow ID. */
@Nonnull public static final String FLOW_ID = "sp/session-initiator";
+
+ /** Discovery service. */
+ @Nonnull public static final String DISCO_URL = "https://ds.example.org/DS";
+
+ /** Return URL for discovery. */
+ @Nonnull public static final String DISCO_RETURN_URL = "https://sp.example.org/Login?DS=1";
protected SessionInitiatorFlowTest() {
super(FLOW_ID);
@@ -91,4 +103,64 @@ public class SessionInitiatorFlowTest extends AbstractSPFlowTest {
assertFlowExecutionOutcome(result.getOutcome());
assertOutputMessageEvent(result, AuthnEventIds.NO_POTENTIAL_FLOW);
}
+
+ /**
+ * Test flow with no initiator subflows installed but discovery triggered and no target resource.
+ * @throws IOException
+ */
+ @Test
+ public void testDiscoveryNoTarget() throws IOException {
+ setDefaultAuth();
+
+ final Escaper esc = UrlEscapers.urlFormParameterEscaper();
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(SPConstants.APPLICATION).string(APPLICATION_ID);
+ input.addmember(InitiatorConstants.DISCOVERY_RETURN_URL).string(esc.escape(DISCO_RETURN_URL));
+ input.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
+ setRequest("POST", input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+
+ final DDF output = assertOutputMessageSuccess(result);
+ final String url = new String(
+ output.getmember(RemotedHttpServletResponse.STRUCTURE_NAME).getmember(RemotedHttpServletResponse.REDIRECT).unsafe_string(),
+ StandardCharsets.UTF_8);
+ assert url != null;
+
+ Assert.assertEquals(url, DISCO_URL + "?return=" + esc.escape(DISCO_RETURN_URL) + "&entityID=" + esc.escape("https://sp.example.org"));
+ }
+
+ /**
+ * Test flow with no initiator subflows installed but discovery triggered and a target resource.
+ * @throws IOException
+ */
+ @Test
+ public void testDiscoveryWithTarget() throws IOException {
+ setDefaultAuth();
+
+ final Escaper esc = UrlEscapers.urlFormParameterEscaper();
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(SPConstants.APPLICATION).string(APPLICATION_ID);
+ input.addmember(InitiatorConstants.DISCOVERY_RETURN_URL).string(esc.escape(DISCO_RETURN_URL));
+ input.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
+ input.addmember(SPConstants.TARGET).unsafe_string("https://sp.example.org".getBytes(StandardCharsets.UTF_8));
+ setRequest("POST", input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+
+ final DDF output = assertOutputMessageSuccess(result);
+ final String url = new String(
+ output.getmember(RemotedHttpServletResponse.STRUCTURE_NAME).getmember(RemotedHttpServletResponse.REDIRECT).unsafe_string(),
+ StandardCharsets.UTF_8);
+ assert url != null;
+
+ Assert.assertTrue(url.startsWith(DISCO_URL + "?return=" + esc.escape(DISCO_RETURN_URL + "&state=")));
+ }
+
}
\ No newline at end of file
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/TestSPEnvironmentApplicationContextInitializer.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/TestSPEnvironmentApplicationContextInitializer.java
index c602e84..21bd025 100644
--- a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/TestSPEnvironmentApplicationContextInitializer.java
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/TestSPEnvironmentApplicationContextInitializer.java
@@ -37,6 +37,9 @@ public class TestSPEnvironmentApplicationContextInitializer extends TestEnvironm
// Avoids some complex mocking for the default StateManager bean.
mock.setProperty("sp.stateToken.sealed", "false");
+ // Set to allow disco flow to run.
+ mock.setProperty("sp.discoveryURL", "https://ds.example.org/DS");
+
mock.setProperty("sp.storageService", "shibboleth.StorageService");
mock.setProperty("sp.session.storageService", "shibboleth.StorageService");
mock.setProperty("idp.additionalProperties",
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/Application.java b/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
index 34901c9..e8e14d8 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
@@ -30,7 +30,6 @@ import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
import net.shibboleth.profile.relyingparty.RelyingPartyConfigurationResolver;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.service.ReloadableService;
-import net.shibboleth.sp.profile.StateTokenManager;
import net.shibboleth.sp.state.StateManager;
/**
@@ -102,16 +101,6 @@ public interface Application extends RelyingPartyConfigurationResolver {
* @return ordered list of unprefixed flow IDs
*/
@Nonnull List<String> getLogoutInitiators(@Nullable final ProfileRequestContext profileRequestContext);
-
- /**
- * Gets the {@link StateTokenManager} to use for thie application.
- *
- * @return the manager to use
- *
- * @deprecated
- */
- @Deprecated
- @Nonnull StateTokenManager getStateTokenManager();
/**
* Gets the {@link StateManager} to use for thie application.
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/context/TokenConsumerContext.java b/sp-server-api/src/main/java/net/shibboleth/sp/context/TokenConsumerContext.java
deleted file mode 100644
index 2b09eff..0000000
--- a/sp-server-api/src/main/java/net/shibboleth/sp/context/TokenConsumerContext.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.context;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.BaseContext;
-
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.primitive.StringSupport;
-
-/**
- * Dedicated context for a token consumer operation's state.
- */
-public class TokenConsumerContext extends BaseContext {
-
- /** A message ID to use for correlating responses against. */
- @Nullable @NotEmpty private String messageCorrelationID;
-
- /**
- * Gets the message ID to correlate responses against.
- *
- * @return original request message ID
- */
- @Nullable @NotEmpty public String getMessageCorrelationID() {
- return messageCorrelationID;
- }
-
- /**
- * Sets the message ID to correlate responses against.
- *
- * @param id original request message ID
- *
- * @return this context
- */
- @Nonnull public TokenConsumerContext setMessageCorrelationID(@Nullable @NotEmpty String id) {
- messageCorrelationID = StringSupport.trimOrNull(id);
-
- return this;
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/AbstractStateTokenManager.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/AbstractStateTokenManager.java
deleted file mode 100644
index 5580661..0000000
--- a/sp-server-api/src/main/java/net/shibboleth/sp/profile/AbstractStateTokenManager.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.profile;
-
-import java.time.Duration;
-
-import javax.annotation.Nonnull;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.security.IdentifierGenerationStrategy;
-import net.shibboleth.shared.security.IdentifierGenerationStrategy.ProviderType;
-
-/**
- * Base class for {@link StateTokenManager} implementations.
- *
- * @deprecated
- */
- at Deprecated
-public abstract class AbstractStateTokenManager extends AbstractIdentifiableInitializableComponent
- implements StateTokenManager {
-
- /** Identifier generation. */
- @NonnullAfterInit private IdentifierGenerationStrategy identifierStrategy;
-
- /** Expiration for state token. */
- @Nonnull private Duration expiration;
-
- /**
- * Constructor.
- */
- @SuppressWarnings("null")
- public AbstractStateTokenManager() {
- expiration = Duration.ofMinutes(30);
- }
-
- /**
- * Get {@link IdentifierGenerationStrategy} to use.
- *
- * @return identifier generator strategy
- */
- @NonnullAfterInit public IdentifierGenerationStrategy getIdentifierGenerationStrategy() {
- return identifierStrategy;
- }
-
- /**
- * Set {@link IdentifierGenerationStrategy} to use.
- *
- * <p>Defaults to a secure random source that produces 16 byte values.</p>
- *
- * @param strategy identifier generator strategy
- */
- public void setIdentifierGenerationStrategy(@Nonnull final IdentifierGenerationStrategy strategy) {
- checkSetterPreconditions();
-
- identifierStrategy = Constraint.isNotNull(strategy, "IdentifierGenerationStrategy cannot be null");
- }
-
- /**
- * Get the expiration limit for state tokens.
- *
- * @return expiration limit
- */
- @Nonnull public Duration getExpiration() {
- return expiration;
- }
-
- /**
- * Set the expiration limit for state tokens.
- *
- * <p>Defaults to PT30M.</p>
- *
- * @param exp expiration limit
- */
- public void setExpiration(@Nonnull final Duration exp) {
- checkSetterPreconditions();
-
- expiration = Constraint.isNotNull(exp, "Expiration cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (identifierStrategy == null) {
- identifierStrategy = IdentifierGenerationStrategy.getInstance(ProviderType.SECURE);
- }
- }
-
- /**
- * Generate a state token.
- *
- * @return a new state token
- */
- @Nonnull protected String generateToken() {
- return identifierStrategy.generateIdentifier(false);
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/AbstractTokenConsumerResponseAction.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/AbstractTokenConsumerResponseAction.java
index f841454..915a463 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/profile/AbstractTokenConsumerResponseAction.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/AbstractTokenConsumerResponseAction.java
@@ -15,6 +15,7 @@
package net.shibboleth.sp.profile;
import java.nio.charset.StandardCharsets;
+import java.time.Instant;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -134,6 +135,11 @@ public abstract class AbstractTokenConsumerResponseAction extends AbstractApplic
output.addmember(ConsumerConstants.SESSION_OPAQUE).structure().add(sessionData);
}
+ final Instant overrideExp = getSessionNotOnOrAfter(profileRequestContext);
+ if (overrideExp != null) {
+ output.addmember(ConsumerConstants.SESSION_NOTONORAFTER).longinteger(overrideExp.getEpochSecond());
+ }
+
final RemotedHttpServletResponse remotedResponse = agentRequestContext.getRemotedHttpServletResponse();
assert remotedResponse != null;
@@ -168,5 +174,16 @@ public abstract class AbstractTokenConsumerResponseAction extends AbstractApplic
* @return opaque session data as a {@link DDF} node, or null if no session data is provided
*/
@Nullable protected abstract DDF getSessionData(@Nonnull final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get a timestamp for the expiration of the session directed by the authentication token, if any.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return session expiration time or null
+ */
+ @Nullable protected Instant getSessionNotOnOrAfter(@Nonnull final ProfileRequestContext profileRequestContext) {
+ return null;
+ }
}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/ConsumerConstants.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/ConsumerConstants.java
index 5ee9ff4..2c903ad 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/profile/ConsumerConstants.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/ConsumerConstants.java
@@ -32,12 +32,12 @@ public final class ConsumerConstants {
/** Opaque session data member. */
@Nonnull @NotEmpty public static final String SESSION_OPAQUE = "session.opaque";
+ /** NotOnOrAfter session member. */
+ @Nonnull @NotEmpty public static final String SESSION_NOTONORAFTER = "session.notonorafter";
+
/** Session attributes data member. */
@Nonnull @NotEmpty public static final String SESSION_ATTRIBUTES = "session.attributes";
- /** Passive indicator output parameter name. */
- @Nonnull @NotEmpty public static final String PASSIVE = "passive";
-
/** Private constructor. */
private ConsumerConstants() {
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/PreserveStateDataAction.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/PreserveStateDataAction.java
index d0fcd0c..a706783 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/profile/PreserveStateDataAction.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/PreserveStateDataAction.java
@@ -60,7 +60,7 @@ public class PreserveStateDataAction extends AbstractApplicationAction {
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(PreserveStateDataAction.class);
- /** Strategy used to create the {@link StateDataContext} to presrve. */
+ /** Strategy used to locate the {@link StateDataContext} to preserve. */
@Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextLookupStrategy;
/** Whether an error constructing a state token is fatal. */
@@ -138,6 +138,7 @@ public class PreserveStateDataAction extends AbstractApplicationAction {
final String token = ensureApplication().getStateManager().preserveToStateToken(
ensureAgent(), ensureApplication(), data);
stateDataContext.setStateToken(token);
+ processToken(profileRequestContext, token);
log.debug("{} State data preserved to token: {}", getLogPrefix(), token);
} catch (final IOException e) {
log.warn("{} Exception preserving state data", getLogPrefix(), e);
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/SPConstants.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/SPConstants.java
index 30b7ccf..b0101bb 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/profile/SPConstants.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/SPConstants.java
@@ -35,6 +35,9 @@ public final class SPConstants {
/** Event parameter. */
@Nonnull @NotEmpty public static final String EVENT = "event";
+ /** Original Event parameter. */
+ @Nonnull @NotEmpty public static final String ORIGINAL_EVENT = "original_event";
+
/** Transaction ID parameter. */
@Nonnull @NotEmpty public static final String TXID = "txid";
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/StateTokenManager.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/StateTokenManager.java
deleted file mode 100644
index f542405..0000000
--- a/sp-server-api/src/main/java/net/shibboleth/sp/profile/StateTokenManager.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.profile;
-
-import java.io.IOException;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import net.shibboleth.sp.Agent;
-import net.shibboleth.sp.Application;
-
-/**
- * Interface to a service that manages "state" tokens, used in most SSO protocols
- * to manage stateful request/response correlation and to limit exposure of the
- * resource URLs accessed by clients to allow recovery of the URL for final redirection.
- *
- * <p>SAML refers to this notion as <em>RelayState</em>, while OpenID Connect just refers to it
- * as <em>state</em>.</p>
- *
- * <p>There are multiple possible implementations of this concept, some involving cookies.</p>
- *
- * <p>The value type is a byte array to accomodate non-Unicode data from agents.</p>
- *
- * @deprecated
- */
- at Deprecated
-public interface StateTokenManager {
-
- /**
- * Preserves a value by transforming it into a state token.
- *
- * @param agent agent owning the state
- * @param application application owning the state
- * @param value input value to preserve
- *
- * @return state token representing value
- *
- * @throws IOException if creation of token fails
- */
- @Nonnull String preserveToStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final byte[] value) throws IOException;
-
- /**
- * Recovers a value from a state token.
- *
- * <p>In most implementations, the state token mapping should be cleared on successful use of this
- * method.</p>
- *
- * @param agent agent owning the state
- * @param application application owning the state
- * @param token state token
- *
- * @return the recovered value, or null if unable to recover without underlying cause
- *
- * @throws IOException if recovery from token fails
- */
- @Nullable byte[] recoverFromStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final String token) throws IOException;
-
-}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/MessageCorrelationIDLookupFunction.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/MessageCorrelationIDLookupFunction.java
deleted file mode 100644
index 1aacff1..0000000
--- a/sp-server-api/src/main/java/net/shibboleth/sp/profile/context/navigate/MessageCorrelationIDLookupFunction.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.profile.context.navigate;
-
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
-
-import net.shibboleth.sp.context.TokenConsumerContext;
-
-/**
- * Lookup strategy for accessing message correlation ID in {@link TokenConsumerContext}.
- */
-public class MessageCorrelationIDLookupFunction implements ContextDataLookupFunction<TokenConsumerContext,String> {
-
- /** {@inheritDoc} */
- @Nullable public String apply(@Nullable final TokenConsumerContext input) {
- return input != null ? input.getMessageCorrelationID() : null;
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/state/AbstractStateManager.java b/sp-server-api/src/main/java/net/shibboleth/sp/state/AbstractStateManager.java
index 4cba0e6..3cac4ab 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/state/AbstractStateManager.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/state/AbstractStateManager.java
@@ -214,9 +214,9 @@ public abstract class AbstractStateManager extends AbstractIdentifiableInitializ
if (localDataSealer != null) {
log.debug("State data will be sealed before preservation");
return doPreserve(agent, application, localDataSealer.wrap(serializedState,
- Instant.now().plus(expiration)));
+ Instant.now().plus(expiration)), true);
} else {
- return doPreserve(agent, application, serializedState);
+ return doPreserve(agent, application, serializedState, false);
}
} catch (final JsonProcessingException | DataSealerException e) {
throw new IOException("Error preserving state", e);
@@ -238,7 +238,7 @@ public abstract class AbstractStateManager extends AbstractIdentifiableInitializ
}
}
- String data = doRecover(agent, application, token);
+ String data = doRecover(agent, application, token, dataSealer != null);
if (data == null) {
return null;
}
@@ -281,13 +281,14 @@ public abstract class AbstractStateManager extends AbstractIdentifiableInitializ
* @param agent agent owning the state
* @param application application owning the state
* @param data data to preserve
+ * @param sealed whether the data was sealed (and thus base64-encoded)
*
* @return the state token
*
* @throws IOException if an error occurs
*/
@Nonnull protected abstract String doPreserve(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final String data) throws IOException;
+ @Nonnull final String data, final boolean sealed) throws IOException;
/**
* Subclasses implement this method to recover the stored data in whatever way is necessary and return
@@ -301,13 +302,14 @@ public abstract class AbstractStateManager extends AbstractIdentifiableInitializ
* @param agent agent owning the state
* @param application application owning the state
* @param stateToken the state token to map from/clear
+ * @param sealed whether the data was sealed (and thus base64-encoded)
*
* @return the recovered data, or null if unable to recover without underlying cause
*
* @throws IOException if an error occurs
*/
@Nullable protected abstract String doRecover(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull @NotEmpty final String stateToken) throws IOException;
+ @Nonnull @NotEmpty final String stateToken, final boolean sealed) throws IOException;
/**
* Generate a state token.
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
index 5f01f6f..379d41a 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
@@ -58,7 +58,6 @@ import net.shibboleth.shared.service.ReloadableService;
import net.shibboleth.sp.Agent;
import net.shibboleth.sp.Application;
import net.shibboleth.sp.ProtocolSupportService;
-import net.shibboleth.sp.profile.StateTokenManager;
import net.shibboleth.sp.state.StateManager;
import net.shibboleth.spring.security.CredentialHolder;
@@ -97,9 +96,6 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
/** Logout initiator list lookup strategy. */
@NonnullAfterInit private Function<ProfileRequestContext,List<String>> logoutInitiatorLookupStrategy;
- /** State token management. */
- @Nullable private StateTokenManager stateTokenManager;
-
/** State management. */
@Nullable private StateManager stateManager;
@@ -382,35 +378,7 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
logoutInitiatorLookupStrategy = Constraint.isNotNull(strategy,
"Logout initiators lookup strategy cannot be null");
}
-
- /** {@inheritDoc} */
- @Nonnull public StateTokenManager getStateTokenManager() {
- checkComponentActive();
- if (stateTokenManager != null) {
- return stateTokenManager;
- }
-
- if (parent != null && allowInheritance) {
- return parent.getStateTokenManager();
- }
-
- throw new ConstraintViolationException("StateTokenManager cannot be null");
- }
-
- /**
- * Set the {@link StateTokenManager} to use.
- *
- * @param manager state token manager
- *
- * @deprecated
- */
- @Deprecated
- public void setStateTokenManager(@Nonnull final StateTokenManager manager) {
- checkSetterPreconditions();
- stateTokenManager = Constraint.isNotNull(manager, "StateTokenManager cannot be null");
- }
-
/** {@inheritDoc} */
@Nonnull public StateManager getStateManager() {
checkComponentActive();
@@ -644,9 +612,7 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
return;
}
- if (stateTokenManager == null) {
- throw new ComponentInitializationException("StateTokenManager cannot be null");
- } else if (stateManager == null) {
+ if (stateManager == null) {
throw new ComponentInitializationException("StateManager cannot be null");
} else if (transcodingRegistry == null) {
throw new ComponentInitializationException("AttributeTranscoderRegistry cannot be null");
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/CookieStateTokenManager.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/CookieStateTokenManager.java
deleted file mode 100644
index ab832d3..0000000
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/CookieStateTokenManager.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * 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.impl;
-
-import java.io.IOException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.NoSuchAlgorithmException;
-import java.time.Instant;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.slf4j.Logger;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.shared.codec.DecodingException;
-import net.shibboleth.shared.codec.EncodingException;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.shared.security.IdentifierGenerationStrategy;
-import net.shibboleth.shared.security.IdentifierGenerationStrategy.ProviderType;
-import net.shibboleth.shared.security.RandomIdentifierParameterSpec;
-import net.shibboleth.sp.Agent;
-import net.shibboleth.sp.Application;
-import net.shibboleth.sp.profile.AbstractStateTokenManager;
-import net.shibboleth.sp.profile.StateTokenManager;
-
-/**
- * {@link StateTokenManager} implemented using cookies.
- *
- * @deprecated
- */
- at Deprecated
-public class CookieStateTokenManager extends AbstractStateTokenManager {
-
- /** Default cookie prefix. */
- @Nonnull @NotEmpty public static String DEFAULT_PREFIX = "_shibsp_state_";
-
- /** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(CookieStateTokenManager.class);
-
- /** Cookie manager. */
- @NonnullAfterInit private CookieManager cookieManager;
-
- /** Fixed prefix for cookie names. */
- @Nonnull @NotEmpty private String cookiePrefix;
-
- /** Constructor. */
- public CookieStateTokenManager() {
- cookiePrefix = DEFAULT_PREFIX;
- }
-
- /**
- * Set the fixed prefix to use for the cookies.
- *
- * <p>Defaults to "_shibsp_state".</p>
- *
- * @param prefix cookie prefix
- */
- public void setCookiePrefix(@Nonnull @NotEmpty final String prefix) {
- checkSetterPreconditions();
-
- cookiePrefix = Constraint.isNotNull(StringSupport.trimOrNull(prefix), "Cookie prefix cannot be null or empty");
- }
-
- /**
- * Set the {@link CookieManager} to use.
- *
- * @param manager instance to use
- */
- public void setCookieManager(@Nonnull final CookieManager manager) {
- checkSetterPreconditions();
-
- cookieManager = Constraint.isNotNull(manager, "CookieManager cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (cookieManager == null) {
- throw new ComponentInitializationException("CookieManager cannot be null");
- }
-
- if (getIdentifierGenerationStrategy() == null) {
- final RandomIdentifierParameterSpec spec = new RandomIdentifierParameterSpec(null, 6, null);
- try {
- setIdentifierGenerationStrategy(IdentifierGenerationStrategy.getInstance(ProviderType.SECURE, spec));
- } catch (final InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
- throw new ComponentInitializationException(e);
- }
- }
- }
-
- /** {@inheritDoc} */
- @Nonnull public String preserveToStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final byte[] value) throws IOException {
-
- cookieManager.purgeStaleCookies(cookiePrefix);
-
- final Instant ts = Instant.now();
- assert ts != null;
-
- final String key = Long.toString(ts.toEpochMilli()) + '_' + generateToken();
- final String name = getCookieName(application, key);
- try {
- cookieManager.addCookie(name, Base64Support.encodeURLSafe(value), (int) getExpiration().toSeconds());
- } catch (final EncodingException e) {
- throw new IOException(e);
- }
-
- log.trace("Created state token mapping from '{}' to value '{}'", name, value);
-
- return key;
- }
-
- /** {@inheritDoc} */
- @Nullable public byte[] recoverFromStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final String token) throws IOException {
-
- if (token.isEmpty()) {
- log.warn("Invalid state token: '{}'", token);
- return null;
- }
-
- final String cookieName = getCookieName(application, token);
- final String cookieValue = cookieManager.getCookieValue(cookieName, null);
-
- if (cookieValue != null) {
- log.trace("Recovered state token mapping from '{}' to value '{}'", token, cookieValue);
- cookieManager.unsetCookie(cookieName);
- try {
- return Base64Support.decodeURLSafe(cookieValue);
- } catch (final DecodingException e) {
- throw new IOException(e);
- }
- }
-
- log.warn("No cookie found matching state token: '{}'", token);
- return null;
- }
-
- /**
- * Computes the name of a new state cookie.
- *
- * @param application the application
- * @param uniquePortion unique portion of name
- *
- * @return cookie name
- */
- @Nonnull public String getCookieName(@Nonnull final Application application, @Nonnull final String uniquePortion) {
-
- // Format is prefix_appId_timestamp_random
- // The timestamp allows them to be sorted for staleness.
-
- final StringBuilder builder = new StringBuilder(cookiePrefix);
- builder.append('_')
- .append(application.getId())
- .append('_')
- .append(uniquePortion);
-
- return builder.toString();
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/PassthroughStateTokenManager.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/PassthroughStateTokenManager.java
deleted file mode 100644
index c00b485..0000000
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/PassthroughStateTokenManager.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.impl;
-
-import java.io.IOException;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.slf4j.Logger;
-
-import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.shared.codec.DecodingException;
-import net.shibboleth.shared.codec.EncodingException;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.Agent;
-import net.shibboleth.sp.Application;
-import net.shibboleth.sp.profile.StateTokenManager;
-
-/**
- * {@link StateTokenManager} implemented as a simple pass-through that doesn't mask the data.
- *
- * @deprecated
- */
- at Deprecated
-public class PassthroughStateTokenManager extends AbstractIdentifiableInitializableComponent
- implements StateTokenManager {
-
- /** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(PassthroughStateTokenManager.class);
-
- /** {@inheritDoc} */
- @Override
- @Nonnull public String preserveToStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final byte[] value) throws IOException {
-
- try {
- return Base64Support.encodeURLSafe(value);
- } catch (final EncodingException e) {
- throw new IOException(e);
- }
- }
-
- /** {@inheritDoc} */
- @Override
- @Nullable public byte[] recoverFromStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final String token) throws IOException {
-
- try {
- return Base64Support.decodeURLSafe(token);
- } catch (final DecodingException e) {
- log.warn("Unable to Base64-decode state token: {}", token);
- return null;
- }
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/StorageServiceStateTokenManager.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/StorageServiceStateTokenManager.java
deleted file mode 100644
index d89a6c1..0000000
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/StorageServiceStateTokenManager.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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.impl;
-
-import java.io.IOException;
-import java.time.Instant;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.storage.StorageRecord;
-import org.opensaml.storage.StorageService;
-import org.slf4j.Logger;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.shared.codec.DecodingException;
-import net.shibboleth.shared.codec.EncodingException;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.Agent;
-import net.shibboleth.sp.Application;
-import net.shibboleth.sp.profile.AbstractStateTokenManager;
-import net.shibboleth.sp.profile.StateTokenManager;
-
-/**
- * {@link StateTokenManager} implemented with a {@link StorageService}.
- *
- * @deprecated
- */
- at Deprecated
-public class StorageServiceStateTokenManager extends AbstractStateTokenManager {
-
- /** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(StorageServiceStateTokenManager.class);
-
- /** Storage back-end. */
- @NonnullAfterInit private StorageService storageService;
-
- /**
- * Set {@link StorageService} to use.
- *
- * @param storage storage service
- */
- public void setStorageService(@Nonnull final StorageService storage) {
- checkSetterPreconditions();
-
- storageService = Constraint.isNotNull(storage, "StorageService cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (storageService == null) {
- throw new ComponentInitializationException("StorageService cannot be null");
- } else if (!storageService.getCapabilities().isServerSide()) {
- throw new ComponentInitializationException("StorageService cannot be client-side");
- }
- }
-
- /** {@inheritDoc} */
- @Override
- @Nonnull public String preserveToStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final byte[] value) throws IOException {
-
- final String context = getContext(agent, application);
- final String key = generateToken();
-
- String encoded;
- try {
- encoded = Base64Support.encode(value, false);
- } catch (final EncodingException e) {
- throw new IOException(e);
- }
-
- if (storageService.create(context, key, encoded, Instant.now().plus(getExpiration()).toEpochMilli())) {
- log.trace("Created state token mapping ('{}', '{}') to value '{}'", context, key, encoded);
- return key;
- }
-
- // Should only happen if the randomizer failed...
- throw new IOException("Unable to create storage record for state token");
- }
-
- /** {@inheritDoc} */
- @Override
- @Nullable public byte[] recoverFromStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final String token) throws IOException {
-
- final String context = getContext(agent, application);
- final StorageRecord<String> record = storageService.read(context, token);
- if (record != null) {
- log.trace("Recovered state token mapping ('{}', '{}') to value '{}'", context, token, record.getValue());
- try {
- storageService.delete(context, token);
- } catch (final IOException e) {
- log.warn("Unable to delete state token ('{}', '{}') from storage", context, token, e);
- }
- try {
- return Base64Support.decode(record.getValue());
- } catch (final DecodingException e) {
- throw new IOException(e);
- }
- }
-
- return null;
- }
-
- /**
- * Compute the storage context to use.
- *
- * @param agent calling agent
- * @param application calling application
- *
- * @return storage context for request
- */
- @SuppressWarnings("null")
- @Nonnull private String getContext(@Nonnull final Agent agent, @Nonnull final Application application) {
- final StringBuilder builder = new StringBuilder(StorageServiceStateTokenManager.class.getName());
- builder.append('!').append(agent.getId()).append('!').append(application.getId());
- return builder.toString();
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/EncodeAgentResponse.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/EncodeAgentResponse.java
index f6ac184..1658d6b 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/EncodeAgentResponse.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/EncodeAgentResponse.java
@@ -16,9 +16,11 @@ package net.shibboleth.sp.profile.impl;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.function.Function;
import javax.annotation.Nonnull;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
@@ -27,18 +29,23 @@ import org.slf4j.Logger;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.sp.Agent;
import net.shibboleth.sp.context.AgentRequestContext;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.profile.AbstractAgentRequestAction;
+import net.shibboleth.sp.profile.SPConstants;
+import net.shibboleth.sp.state.StateData;
/**
* A profile action to encode an agent response from the output {@link DDF} in the
* {@link AgentRequestContext}.
*
* <p>This also ensures the final output is a structure, and contains an "event" member,
- * setting it to "success" if not already set.</p>
+ * setting it to "success" if not already set, and also populates target from a {@link StateDataContext}
+ * if available.</p>
*
* <p>If enabled as a feature for the agent, the value of the session ID is also placed
* in a structure member in the output before encoding to relay it to the agent.</p>
@@ -47,16 +54,37 @@ import net.shibboleth.sp.profile.AbstractAgentRequestAction;
* @event {@link EventIds#INVALID_PROFILE_CTX}
* @event {@link EventIds#INVALID_MESSAGE}
* @event {@link EventIds#IO_ERROR}
- * @pre <pre>AgentRequestContext.getOutput().getmember("event").isstring()</pre>
+ *
+ * @post <pre>AgentRequestContext.getOutput().getmember("event").isstring()</pre>
*/
public class EncodeAgentResponse extends AbstractAgentRequestAction {
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(EncodeAgentResponse.class);
+ /** Strategy used to create the {@link StateDataContext} to populate. */
+ @Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextLookupStrategy;
+
/** Cached context containing the output message. */
@NonnullBeforeExec private DDF outputMessage;
+ /** Constructor. */
+ public EncodeAgentResponse() {
+ stateDataContextLookupStrategy = new ChildContextLookup<>(StateDataContext.class);
+ }
+
+ /**
+ * Sets the strategy used to lookup the {@link StateDataContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setStateDataContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,StateDataContext> strategy) {
+ checkSetterPreconditions();
+ stateDataContextLookupStrategy =
+ Constraint.isNotNull(strategy, "StateDataContext creation strategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -94,8 +122,22 @@ public class EncodeAgentResponse extends AbstractAgentRequestAction {
return;
}
- if (outputMessage.getmember("event").isnull()) {
- outputMessage.addmember("event").string("success");
+ if (outputMessage.getmember(SPConstants.EVENT).isnull()) {
+ outputMessage.addmember(SPConstants.EVENT).string("success");
+ }
+
+ // Populate target if necessary.
+ if (outputMessage.getmember(SPConstants.TARGET).isnull()) {
+ final StateDataContext stateDataContext = stateDataContextLookupStrategy.apply(profileRequestContext);
+ if (stateDataContext != null) {
+ final StateData data = stateDataContext.getStateData();
+ if (data != null) {
+ final byte[] target = data.getRawResource();
+ if (target != null) {
+ outputMessage.addmember(SPConstants.TARGET).unsafe_string(target);
+ }
+ }
+ }
}
final Agent agent = ensureAgentRequestContext().getAgent();
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueCorrelationCookie.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueCorrelationCookie.java
deleted file mode 100644
index f635187..0000000
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueCorrelationCookie.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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.profile.impl;
-
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.google.common.escape.Escaper;
-import com.google.common.net.UrlEscapers;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.sp.context.AgentRequestContext;
-import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
-import net.shibboleth.sp.profile.AbstractApplicationAction;
-import net.shibboleth.sp.profile.SPConstants;
-
-/**
- * Action that issues a cookie used to record state about a request for
- * later enforcement/evaluation.
- *
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link EventIds#INVALID_MESSAGE}
- */
-public class IssueCorrelationCookie extends AbstractApplicationAction {
-
- /** Default cookie prefix. */
- @Nonnull @NotEmpty static public String DEFAULT_COOKIE_PREFIX = "shibsp_req_";
-
- /** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(IssueCorrelationCookie.class);
-
- /** Cookie manager. */
- @NonnullAfterInit private CookieManager cookieManager;
-
- /** Cookie prefix. */
- @Nonnull private String cookiePrefix;
-
- /** Whether an error constructing a correlation cookie is fatal. */
- private boolean errorFatal;
-
- /** Lookup strategy for request message ID. */
- @NonnullAfterInit private Function<ProfileRequestContext,String> requestIDLookupStrategy;
-
- /** State token vakue used in cookie name. */
- @NonnullBeforeExec private String stateToken;
-
- /** Request ID. */
- @NonnullBeforeExec private String requestID;
-
- /** Constructor. */
- public IssueCorrelationCookie() {
- cookiePrefix = DEFAULT_COOKIE_PREFIX;
- }
-
- /**
- * Sets the {@link CookieManager} to use.
- *
- * @param manager cookie manager instance
- */
- public void setCookieManager(@Nonnull final CookieManager manager) {
- checkSetterPreconditions();
-
- cookieManager = Constraint.isNotNull(manager, "CookieManager cannot be null");
- }
-
- /**
- * Sets the cookie prefix.
- *
- * <p>Defaults to {@link #DEFAULT_COOKIE_PREFIX}.</p>
- *
- * @param prefix cookie prefix
- */
- public void setCookiePrefix(@Nonnull @NotEmpty final String prefix) {
- checkSetterPreconditions();
-
- cookiePrefix = Constraint.isNotNull(StringSupport.trimOrNull(prefix), "Cookie prefix cannot be null or empty");
- }
-
- /**
- * Sets whether an error computing a state token should result in a fatal event.
- *
- * <p>Defaults to false.</p>
- *
- * @param flag flag to set
- */
- public void setErrorFatal(final boolean flag) {
- checkSetterPreconditions();
-
- errorFatal = flag;
- }
-
- /**
- * Sets the lookup strategy for obtaining the request message's ID.
- *
- * @param strategy lookup strategy
- */
- public void setRequestIDLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
- checkSetterPreconditions();
-
- requestIDLookupStrategy = Constraint.isNotNull(strategy, "Request ID lookup strategy cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (cookieManager == null) {
- throw new ComponentInitializationException("CookieManager cannot be null");
- } else if (requestIDLookupStrategy == null) {
- throw new ComponentInitializationException("Request ID lookup strategy cannot be null");
- }
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- if (!super.doPreExecute(profileRequestContext)) {
- return false;
- }
-
- requestID = requestIDLookupStrategy.apply(profileRequestContext);
-
- if (requestID == null) {
- log.debug("{} No request message ID available, skipping creation of correlation cookie", getLogPrefix());
- return false;
- }
-
- final DDF input = ensureAgentRequestContext().getInput();
- if (input != null) {
- stateToken = input.getmember(SPConstants.STATE).string();
- }
-
- if (stateToken == null) {
- if (requestID != null) {
- if (errorFatal) {
- log.warn("{} Input was missing {} parameter", getLogPrefix(), SPConstants.STATE);
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- } else {
- log.debug("{} Input was missing {} parameter, skipping creation of correlation cookie", getLogPrefix(),
- SPConstants.STATE);
- }
- }
- return false;
- }
-
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- ensureOutputObjects();
- final AgentRequestContext agentRequestContext = ensureAgentRequestContext();
-
- // We do the crazy stuff to accomodate the cookies being set or unset.
- try {
- RemotedHttpServletRequestResponseContext.loadCurrent(agentRequestContext.getRemotedHttpServletRequest(),
- agentRequestContext.getRemotedHttpServletResponse());
-
- cookieManager.purgeStaleCookies(cookiePrefix);
-
- log.debug("{} Tracking request ID {} against state token {}", getLogPrefix(), requestID, stateToken);
-
- final Escaper escaper = UrlEscapers.urlFormParameterEscaper();
- cookieManager.addCookie(cookiePrefix + escaper.escape(stateToken), escaper.escape(requestID));
- } finally {
- RemotedHttpServletRequestResponseContext.clearCurrent();
- }
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequest.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequest.java
index 52d9ddc..97b44ba 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequest.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequest.java
@@ -21,6 +21,7 @@ import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
@@ -35,6 +36,7 @@ import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.resolver.CriteriaSet;
import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
import net.shibboleth.sp.profile.AbstractApplicationAction;
@@ -61,6 +63,9 @@ public class IssueDiscoveryRequest extends AbstractApplicationAction {
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(IssueDiscoveryRequest.class);
+ /** Strategy used to create the {@link StateDataContext} to populate. */
+ @Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextLookupStrategy;
+
/** Strategy used to obtain the request issuer value. */
@Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
@@ -79,15 +84,25 @@ public class IssueDiscoveryRequest extends AbstractApplicationAction {
/** The agent handler URL to return control to. */
@NonnullBeforeExec private String returnURL;
- /** State token. */
- @Nullable private String state;
-
/** Constructor. */
public IssueDiscoveryRequest() {
+ stateDataContextLookupStrategy = new ChildContextLookup<>(StateDataContext.class);
issuerLookupStrategy = new ApplicationDefaultIssuerStrategy();
passiveCondition = new DefaultPassiveCondition();
}
+ /**
+ * Sets the strategy used to lookup the {@link StateDataContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setStateDataContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,StateDataContext> strategy) {
+ checkSetterPreconditions();
+ stateDataContextLookupStrategy =
+ Constraint.isNotNull(strategy, "StateDataContext creation strategy cannot be null");
+ }
+
/**
* Set the strategy used to locate the issuer value to use.
*
@@ -97,7 +112,7 @@ public class IssueDiscoveryRequest extends AbstractApplicationAction {
checkSetterPreconditions();
issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
}
-
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -126,9 +141,7 @@ public class IssueDiscoveryRequest extends AbstractApplicationAction {
log.error("{} No input message from agent", getLogPrefix());
return false;
}
-
- state = input.getmember(SPConstants.STATE).string();
-
+
// This is checked earlier in the flow, so can be treated as an outright error here.
returnURL = input.getmember(InitiatorConstants.DISCOVERY_RETURN_URL).string();
if (returnURL == null) {
@@ -141,15 +154,18 @@ public class IssueDiscoveryRequest extends AbstractApplicationAction {
}
/** {@inheritDoc} */
- @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- // We have to add the state parameter (usually placed there by our calling flow) to the query
- // string of the already-URL-encoded return URL. We URL-encode the character sequence
- // "&state=<value>" and append that to the existing URL. This ensures the agent will get our
- // state token back from the DS to provide to us afterwards.
-
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
final Escaper escaper = UrlEscapers.urlFormParameterEscaper();
+
+ // Check for state token in context.
+ final StateDataContext stateDataContext = stateDataContextLookupStrategy.apply(profileRequestContext);
+ final String state = stateDataContext != null ? stateDataContext.getStateToken() : null;
if (state != null) {
+ // We have to add a state parameter to the query string of the already-URL-encoded return URL.
+ // We URL-encode the character sequence "&state=<value>" and append that to the existing URL.
+ // This ensures the agent will get our state token back from the DS to provide to the Hub afterwards.
+
returnURL += escaper.escape('&' + SPConstants.STATE + '=' + escaper.escape(state));
// At this point, the return value is already encoded, and thus does not need to
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/MapResourceToStateToken.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/MapResourceToStateToken.java
deleted file mode 100644
index d09afca..0000000
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/MapResourceToStateToken.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.profile.impl;
-
-import java.io.IOException;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.context.AgentRequestContext;
-import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
-import net.shibboleth.sp.profile.AbstractApplicationAction;
-import net.shibboleth.sp.profile.SPConstants;
-
-/**
- * Action that maps a target/resource URL into a state token.
- *
- * <p>The existence of a {@link SPConstants#STATE} member will cause the action
- * to be skipped, while the absence of {@link SPConstants#TARGET} will result in
- * conditional failure.</p>
- *
- * <p>Errors may be ignored or result in an {@link EventIds#IO_ERROR} event.</p>
- *
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link EventIds#INVALID_MESSAGE}
- * @event {@link EventIds#IO_ERROR}
- */
-public class MapResourceToStateToken extends AbstractApplicationAction {
-
- /** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(MapResourceToStateToken.class);
-
- /** Whether an error constructing a state token is fatal. */
- private boolean errorFatal;
-
- /** Whether to require target parameter in the absence of a state token. */
- private boolean targetRequired;
-
- /** Agent input. */
- @NonnullBeforeExec private DDF input;
-
- /** Target resource to operate on. */
- @NonnullBeforeExec private byte[] target;
-
- /** Constructor. */
- public MapResourceToStateToken() {
- targetRequired = true;
- }
-
- /**
- * Sets whether an error computing a state token should result in a fatal event.
- *
- * <p>Defaults to false.</p>
- *
- * @param flag flag to set
- */
- public void setErrorFatal(final boolean flag) {
- checkSetterPreconditions();
-
- errorFatal = flag;
- }
-
- /**
- * Sets whether the {@link SPConstants#TARGET} parameter is required if no state token
- * exists already.
- *
- * <p>Defaults to true.</p>
- *
- * @param flag flag to set
- */
- public void setTargetRequired(final boolean flag) {
- checkSetterPreconditions();
-
- targetRequired = flag;
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- if (!super.doPreExecute(profileRequestContext)) {
- return false;
- }
-
- input = ensureAgentRequestContext().getInput();
- if (input == null) {
- log.debug("{} Input message was absent", getLogPrefix());
- return false;
- }
-
- if (input.getmember(SPConstants.STATE).isstring()) {
- log.debug("{} Input message already contains {} parameter", getLogPrefix(), SPConstants.STATE);
- return false;
- }
-
- target = input.getmember(SPConstants.TARGET).unsafe_string();
- if (target == null) {
- if (targetRequired) {
- log.warn("{} Input message did not contain {} member", getLogPrefix(), SPConstants.TARGET);
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- }
- return false;
- }
-
- ensureAgentRequestContext().setTargetURL(target);
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- ensureOutputObjects();
-
- final AgentRequestContext agentRequestContext = ensureAgentRequestContext();
-
- // We do the crazy stuff to accomodate cookie-backed state management.
- try {
- RemotedHttpServletRequestResponseContext.loadCurrent(agentRequestContext.getRemotedHttpServletRequest(),
- agentRequestContext.getRemotedHttpServletResponse());
-
- try {
- final String token = ensureApplication().getStateTokenManager().preserveToStateToken(
- ensureAgent(), ensureApplication(), target);
- input.addmember(SPConstants.STATE).string(token);
- log.debug("{} Target resource preserved to state token: {}", getLogPrefix(), token);
- } catch (final IOException e) {
- log.warn("{} Exception preserving target resource to state token", getLogPrefix(), e);
- if (errorFatal) {
- ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
- }
- }
- } finally {
- RemotedHttpServletRequestResponseContext.clearCurrent();
- }
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/MapStateTokenToResource.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/MapStateTokenToResource.java
deleted file mode 100644
index 99ae50b..0000000
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/MapStateTokenToResource.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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.profile.impl;
-
-import java.io.IOException;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.context.AgentRequestContext;
-import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
-import net.shibboleth.sp.profile.AbstractApplicationAction;
-import net.shibboleth.sp.profile.SPConstants;
-
-/**
- * Action that maps a state token into a target/resource URL.
- *
- * <p>The state token comes from a pluggable function.</p>
- *
- * <p>The existence of {@link AgentRequestContext#getTargetURL()} will cause the action
- * to be skipped but the absence of a state token will not result in failure.</p>
- *
- * <p>As a side effect for downstream actions, the state token will be added to the
- * input message in a {@link SPConstants#STATE} field.</p>
- *
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link EventIds#INVALID_MESSAGE}
- * @event {@link EventIds#IO_ERROR}
- */
-public class MapStateTokenToResource extends AbstractApplicationAction {
-
- /** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(MapStateTokenToResource.class);
-
- /** Lookup strategy for state token. */
- @NonnullAfterInit private Function<ProfileRequestContext,String> stateTokenLookupStrategy;
-
- /**
- * Sets the lookup strategy to obtain the protocol specific state token.
- *
- * @param strategy lookup strategy
- */
- public void setStateTokenLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
- checkSetterPreconditions();
- stateTokenLookupStrategy = Constraint.isNotNull(strategy, "State token lookup strategy cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (stateTokenLookupStrategy == null) {
- throw new ComponentInitializationException("State token lookup strategy cannot be null");
- }
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- if (!super.doPreExecute(profileRequestContext)) {
- return false;
- }
-
- if (ensureAgentRequestContext().getTargetURL() != null) {
- log.debug("{} Target URL already populated, skipping state token processing", getLogPrefix());
- return false;
- }
-
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- ensureOutputObjects();
-
- final AgentRequestContext agentRequestContext = ensureAgentRequestContext();
-
- // We do the crazy stuff to accomodate cookie-backed state management
- // (and to get the relevant state token in the first place).
- try {
- RemotedHttpServletRequestResponseContext.loadCurrent(agentRequestContext.getRemotedHttpServletRequest(),
- agentRequestContext.getRemotedHttpServletResponse());
-
- final String token = stateTokenLookupStrategy.apply(profileRequestContext);
- if (token == null) {
- log.debug("{} No state token returned from lookup strategy, nothing to do", getLogPrefix());
- return;
- }
-
- final DDF input = agentRequestContext.getInput();
- if (input != null) {
- input.addmember(SPConstants.STATE).string(token);
- }
-
- final byte[] target = ensureApplication().getStateTokenManager().recoverFromStateToken(
- ensureAgent(), ensureApplication(), token);
- if (target != null) {
- agentRequestContext.setTargetURL(target);
- log.debug("{} Requested resource recovered from state token: {}", getLogPrefix(), target);
- } else {
- log.warn("{} Unable to recover requested resource from state token", getLogPrefix());
- }
- } catch (final IOException e) {
- log.warn("{} Exception recovering requested resource from state token", getLogPrefix(), e);
- } finally {
- RemotedHttpServletRequestResponseContext.clearCurrent();
- }
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PrepareAgentErrorResponse.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PrepareAgentErrorResponse.java
index 61d4a7e..3ba6a6e 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PrepareAgentErrorResponse.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PrepareAgentErrorResponse.java
@@ -24,11 +24,14 @@ import org.opensaml.profile.context.EventContext;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.CurrentOrPreviousEventLookup;
+import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.sp.context.AgentRequestContext;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.profile.AbstractAgentRequestAction;
import net.shibboleth.sp.profile.SPConstants;
+import net.shibboleth.sp.state.StateData;
/**
* A profile action to encode the current event (typically an error) into an agent response in a
@@ -45,10 +48,14 @@ public class PrepareAgentErrorResponse extends AbstractAgentRequestAction {
/** Strategy function for access to {@link EventContext} to check. */
@Nonnull private Function<ProfileRequestContext,EventContext> eventContextLookupStrategy;
+ /** Strategy used to create the {@link StateDataContext} to populate. */
+ @Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextLookupStrategy;
+
/** Constructor. */
public PrepareAgentErrorResponse() {
setAgentRequestContextLookupStrategy(new ChildContextLookup<>(AgentRequestContext.class, true));
eventContextLookupStrategy = new CurrentOrPreviousEventLookup();
+ stateDataContextLookupStrategy = new ChildContextLookup<>(StateDataContext.class);
}
/**
@@ -62,6 +69,18 @@ public class PrepareAgentErrorResponse extends AbstractAgentRequestAction {
eventContextLookupStrategy = Constraint.isNotNull(strategy, "EventContext lookup strategy cannot be null");
}
+ /**
+ * Sets the strategy used to lookup the {@link StateDataContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setStateDataContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,StateDataContext> strategy) {
+ checkSetterPreconditions();
+ stateDataContextLookupStrategy =
+ Constraint.isNotNull(strategy, "StateDataContext creation strategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -77,15 +96,31 @@ public class PrepareAgentErrorResponse extends AbstractAgentRequestAction {
final EventContext eventCtx = eventContextLookupStrategy.apply(profileRequestContext);
final Object event = eventCtx != null ? eventCtx.getEvent() : null;
if (event != null) {
- final String eventString = event.toString();
- output.addmember(SPConstants.EVENT).string(eventString);
+ output.addmember(SPConstants.EVENT).string(event.toString());
} else {
output.addmember(SPConstants.EVENT).string(EventIds.MESSAGE_PROC_ERROR);
}
-
- final byte[] target = agentContext.getTargetURL();
- if (target != null) {
- output.addmember(SPConstants.TARGET).unsafe_string(target);
+
+ // We use the StateData, if it exists, as a last-ditch source of the target parameter
+ // and to determine whether to override the event to NoPassive.
+ final StateDataContext stateDataContext = stateDataContextLookupStrategy.apply(profileRequestContext);
+ final StateData data = stateDataContext != null ? stateDataContext.getStateData() : null;
+
+ final Boolean passive = data != null ? data.getPassive() : null;
+ if (passive != null && passive) {
+ // Remap event and capture the original.
+ output.addmember(SPConstants.ORIGINAL_EVENT).string(output.getmember(SPConstants.EVENT).string());
+ output.getmember(SPConstants.EVENT).string(AuthnEventIds.NO_PASSIVE);
+ }
+
+ // Populate target if necessary.
+ if (output.getmember(SPConstants.TARGET).isnull()) {
+ if (data != null) {
+ final byte[] target = data.getRawResource();
+ if (target != null) {
+ output.addmember(SPConstants.TARGET).unsafe_string(target);
+ }
+ }
}
}
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PrepareDiscoveryStateDataContext.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PrepareDiscoveryStateDataContext.java
new file mode 100644
index 0000000..4d1ab30
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PrepareDiscoveryStateDataContext.java
@@ -0,0 +1,114 @@
+/*
+ * 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.profile.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.context.StateDataContext;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.profile.AbstractApplicationAction;
+import net.shibboleth.sp.profile.SPConstants;
+import net.shibboleth.sp.state.StateData;
+
+/**
+ * Extracts target resource URL from input message and prepares a {@link StateDataContext} for
+ * subsequent preservation handling.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ *
+ * @post StateDataContext exists and {@link StateDataContext#getStateData()} != null and
+ * {@link StateData#getResource()} != null
+ */
+public class PrepareDiscoveryStateDataContext extends AbstractApplicationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PrepareDiscoveryStateDataContext.class);
+
+ /** Strategy used to create the {@link StateDataContext} to populate. */
+ @Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextCreationStrategy;
+
+ /** Input message. */
+ @NonnullBeforeExec private byte[] target;
+
+ /** Constructor. */
+ public PrepareDiscoveryStateDataContext() {
+ stateDataContextCreationStrategy = new ChildContextLookup<>(StateDataContext.class, true);
+ }
+
+ /**
+ * Sets the strategy used to create the {@link StateDataContext}.
+ *
+ * @param strategy creation strategy
+ */
+ public void setStateDataContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,StateDataContext> strategy) {
+ checkSetterPreconditions();
+ stateDataContextCreationStrategy =
+ Constraint.isNotNull(strategy, "StateDataContext creation strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ final DDF input = ensureAgentRequestContext().getInput();
+ if (input == null) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ log.error("{} No input message from agent", getLogPrefix());
+ return false;
+ }
+
+ target = input.getmember(SPConstants.TARGET).unsafe_string();
+ if (target == null) {
+ log.debug("{} No target parameter from agent, nothing to do", getLogPrefix());
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final StateDataContext context = stateDataContextCreationStrategy.apply(profileRequestContext);
+ if (context == null) {
+ log.error("{} Failed to create StateDataContext", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+
+ final StateData data = new StateData();
+ data.setRawResource(target);
+
+ context.setStateData(data);
+ }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PreservePostData.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PreservePostData.java
index 1909e8d..ef90e40 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PreservePostData.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/PreservePostData.java
@@ -18,16 +18,19 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
+import java.util.function.Function;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.storage.StorageRecord;
import org.opensaml.storage.StorageService;
import org.slf4j.Logger;
-import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
@@ -42,6 +45,7 @@ import net.shibboleth.shared.primitive.StringSupport;
import net.shibboleth.shared.security.IdentifierGenerationStrategy;
import net.shibboleth.shared.security.IdentifierGenerationStrategy.ProviderType;
import net.shibboleth.sp.context.AgentRequestContext;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
@@ -52,6 +56,12 @@ import net.shibboleth.sp.profile.SPConstants;
* Action that detects submitted form data, and when permitted, stores it in a {@link StorageService} and
* issues a cookie associated with the active state token to preserve a pointer to the data for recovery.
*
+ * <p>The state token to bind to is collected from a {@link StateDataContext} accessed via lookup strategy.</p>
+ *
+ * <p>The action also has support for detecting data previously preserved by this action by checking for
+ * a {@link SPConstants#STATE} member in the input message and using that to recover the data and clean up
+ * that record so it can be re-associated with the new state token found in the context.</p>
+ *
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#INVALID_MESSAGE}
* @event {@link EventIds#INVALID_PROFILE_CTX}
@@ -60,11 +70,14 @@ import net.shibboleth.sp.profile.SPConstants;
public class PreservePostData extends AbstractApplicationAction {
/** Default cookie prefix. */
- @Nonnull @NotEmpty static public String DEFAULT_COOKIE_PREFIX = "_shibsp_post_";
+ @Nonnull @NotEmpty static public String DEFAULT_COOKIE_PREFIX = "_shibsp_post";
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(PreservePostData.class);
+ /** Strategy used to create the {@link StateDataContext} to bind to. */
+ @Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextLookupStrategy;
+
/** Identifier generation. */
@NonnullAfterInit private IdentifierGenerationStrategy identifierStrategy;
@@ -86,14 +99,27 @@ public class PreservePostData extends AbstractApplicationAction {
/** State token vakue used in cookie name. */
@NonnullBeforeExec private String stateToken;
- /** POST data to preserve. */
- @NonnullBeforeExec private byte[] postData;
+ /** POST data to preserve in record. */
+ @NonnullBeforeExec private String postData;
/** Constructor. */
public PreservePostData() {
- lifetime = Duration.ofMinutes(5);
+ stateDataContextLookupStrategy = new ChildContextLookup<>(StateDataContext.class);
+ lifetime = Duration.ofMinutes(30);
cookiePrefix = DEFAULT_COOKIE_PREFIX;
}
+
+ /**
+ * Sets the strategy used to lookup the {@link StateDataContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setStateDataContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,StateDataContext> strategy) {
+ checkSetterPreconditions();
+ stateDataContextLookupStrategy =
+ Constraint.isNotNull(strategy, "StateDataContext creation strategy cannot be null");
+ }
/**
* Set {@link IdentifierGenerationStrategy} to use.
@@ -196,28 +222,15 @@ public class PreservePostData extends AbstractApplicationAction {
return false;
}
- // First see if anything is even there.
- final DDF input = ensureAgentRequestContext().getInput();
- if (input != null) {
- postData = input.getmember(RemotedHttpServletRequest.STRUCTURE_NAME)
- .getmember(RemotedHttpServletRequest.BODY)
- .unsafe_string();
- }
-
+ postData = getSubmittedData();
if (postData == null) {
- log.debug("{} No POST data to preserve", getLogPrefix());
- return false;
- }
-
- assert input != null;
- final String contentType = input.getmember(RemotedHttpServletRequest.STRUCTURE_NAME)
- .getmember(RemotedHttpServletRequest.CONTENT_TYPE)
- .string();
- if (!"application/x-www-form-urlencoded".equals(contentType)) {
- log.warn("{} Unsupported content type: {}", getLogPrefix(), contentType);
- return false;
+ postData = getPreviousData(profileRequestContext);
+ if (postData == null) {
+ log.debug("{} No POST data to preserve", getLogPrefix());
+ return false;
+ }
}
-
+
// Check permission.
if (!ensureAgent().isSupportsPostPreservation()) {
@@ -226,26 +239,21 @@ public class PreservePostData extends AbstractApplicationAction {
}
long limit = ensureAgent().getPostLimit();
- if (limit > 0 && postData.length > limit) {
+ if (limit > 0 && postData.length() > limit) {
log.warn("{} POST data supplied for preservation, but size {} exceeds agent limit", getLogPrefix(),
- postData.length);
+ postData.length());
return false;
}
// Check for state token for cookie correlation.
-
- if (input != null) {
- stateToken = input.getmember(SPConstants.STATE).string();
- }
-
+ final StateDataContext stateDataContext = stateDataContextLookupStrategy.apply(profileRequestContext);
+ stateToken = stateDataContext != null ? stateDataContext.getStateToken() : null;
if (stateToken == null) {
if (errorFatal) {
- log.warn("{} Input was missing {} parameter, failing due to POST data preservationr",
- getLogPrefix(), SPConstants.STATE);
+ log.warn("{} Context missing state token to bind to, failing POST data preservationr", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
} else {
- log.warn("{} Input was missing {} parameter, skipping POST data preservation", getLogPrefix(),
- SPConstants.STATE);
+ log.warn("{} Context missing state token to bind to, skipping POST data preservation", getLogPrefix());
}
return false;
}
@@ -266,17 +274,13 @@ public class PreservePostData extends AbstractApplicationAction {
cookieManager.purgeStaleCookies(cookiePrefix);
- log.debug("{} Preserving {} bytes of POST data against state token {}", getLogPrefix(), postData.length,
+ log.debug("{} Preserving {} bytes of POST data against state token {}", getLogPrefix(), postData.length(),
stateToken);
- // Given the MIME type, we assume the POST data is URL-encoded, ergo safe to translate as UTF-8.
- // At this stage the underlying data may not in fact be UTF-8 but the encoded characters are ASCII.
- final String encoded = new String(postData, StandardCharsets.UTF_8);
-
// Generate a storage key.
final String key = identifierStrategy.generateIdentifier(false);
- if (!storageService.create(ensureAgent().getId() + ".PostData", key, encoded,
+ if (!storageService.create(ensureAgent().getId() + ".PostData", key, postData,
Instant.now().plus(lifetime).toEpochMilli())) {
log.warn("{} Collision attempting to create storage record for POST data under key {}", getLogPrefix(), key);
if (errorFatal) {
@@ -290,8 +294,7 @@ public class PreservePostData extends AbstractApplicationAction {
}
// Save off cookie. Name is decorated with state token prefix, value is the storage key.
- final Escaper escaper = UrlEscapers.urlFormParameterEscaper();
- cookieManager.addCookie(cookiePrefix + escaper.escape(stateToken), escaper.escape(key));
+ cookieManager.addCookie(getCookieName(stateToken), UrlEscapers.urlFormParameterEscaper().escape(key));
} catch (final IOException e) {
log.error("{} Error creating storage record for POST data", getLogPrefix(), e);
@@ -303,4 +306,119 @@ public class PreservePostData extends AbstractApplicationAction {
}
}
+ /**
+ * Gets data submitted with this request, if any.
+ *
+ * @return submitted data or null
+ */
+ @Nullable private String getSubmittedData() {
+
+ // First see if anything fresh is present, indicating a POST.
+ byte[] raw = null;
+
+ final DDF input = ensureAgentRequestContext().getInput();
+ if (input != null) {
+ raw = input.getmember(RemotedHttpServletRequest.STRUCTURE_NAME)
+ .getmember(RemotedHttpServletRequest.BODY)
+ .unsafe_string();
+ }
+
+ if (raw == null) {
+ return null;
+ }
+
+ assert input != null;
+ final String contentType = input.getmember(RemotedHttpServletRequest.STRUCTURE_NAME)
+ .getmember(RemotedHttpServletRequest.CONTENT_TYPE)
+ .string();
+ if (!"application/x-www-form-urlencoded".equals(contentType)) {
+ log.warn("{} Unsupported content type: {}", getLogPrefix(), contentType);
+ return null;
+ }
+
+ // Given the MIME type, we assume the POST data is URL-encoded, ergo safe to translate as UTF-8.
+ // At this stage the underlying data may not in fact be UTF-8 but the encoded characters are ASCII.
+ return new String(raw, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Checks for previously preserved data tracked against a state token submitted by the agent.
+ *
+ * <p>This case is triggered by use of discovery, which would have preserved the data against the
+ * state token issued at that time, which we have since recovered and disposed of.</p>
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return previous data or null
+ */
+ @Nullable private String getPreviousData(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final AgentRequestContext agentRequestContext = ensureAgentRequestContext();
+
+ final DDF input = agentRequestContext.getInput();
+ String previousStateToken = input != null ? input.getmember(SPConstants.STATE).string() : null;
+ if (previousStateToken == null) {
+ return null;
+ }
+
+ // Implement an "inline" recovery of the data to get it back and clear the original storage record.
+
+ if (previousStateToken.length() > 16) {
+ previousStateToken = previousStateToken.substring(0, 16);
+ }
+
+ // Do the needful to allow cookies to be loaded/cleared from wrapped input.
+ try {
+ RemotedHttpServletRequestResponseContext.loadCurrent(agentRequestContext.getRemotedHttpServletRequest(),
+ agentRequestContext.getRemotedHttpServletResponse());
+
+ // Check for cookie to get storage key.
+ final String key = cookieManager.getCookieValue(getCookieName(previousStateToken), null);
+ if (key == null) {
+ log.debug("{} No recovery cookie for state token {}, skipping POST recovery", getLogPrefix(),
+ previousStateToken);
+ return null;
+ }
+
+ // Unset the cookie.
+ cookieManager.unsetCookie(getCookieName(previousStateToken));
+
+ // Try and read/delete the storage record.
+
+ final StorageRecord<String> record = storageService.read(ensureAgent().getId() + ".PostData", key);
+ if (record == null) {
+ log.warn("{} POST recovery record was missing for key: {}", getLogPrefix(), key);
+ return null;
+ }
+
+ try {
+ storageService.delete(ensureAgent().getId() + ".PostData", key);
+ } catch (final IOException e) {
+ log.warn("{} Error deleting POST recovery record for key: {}", getLogPrefix(), key);
+ }
+
+ return record.getValue();
+
+ } catch (final IOException e) {
+ log.error("{} Error reading storage record for POST data", getLogPrefix(), e);
+ } finally {
+ RemotedHttpServletRequestResponseContext.clearCurrent();
+ }
+
+ return null;
+ }
+
+ /**
+ * Computes the cookie name based on the configured prefix and state token.
+ *
+ * <p>Package private access for unit tests.</p>
+ *
+ * @param stateToken the state token
+ *
+ * @return the cookie name
+ */
+ @Nonnull String getCookieName(@Nonnull final String stateToken) {
+ return cookiePrefix + '_' + UrlEscapers.urlFormParameterEscaper().escape(stateToken);
+ }
+
}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/ProcessCorrelationCookie.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/ProcessCorrelationCookie.java
deleted file mode 100644
index 8dc0fa0..0000000
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/ProcessCorrelationCookie.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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.profile.impl;
-
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.google.common.escape.Escaper;
-import com.google.common.net.UrlEscapers;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.shared.net.URISupport;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.sp.context.AgentRequestContext;
-import net.shibboleth.sp.context.TokenConsumerContext;
-import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
-import net.shibboleth.sp.profile.AbstractApplicationAction;
-
-/**
- * Action that processes a previously issued correlation cookie submitted with the request and
- * extracts the passive status and message ID for use by subsequent validation steps.
- *
- * <p>The information captured is stored in a {@link TokenConsumerContext} child of the
- * {@link AgentRequestContext}.</p>
- *
- * <p>The absence of state or the cookie are not treated as an error or fatal at this stage
- * due to the existence of unsolicited responses.</p>
- *
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link EventIds#INVALID_MESSAGE}
- */
-public class ProcessCorrelationCookie extends AbstractApplicationAction {
-
- /** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(ProcessCorrelationCookie.class);
-
- /** Cookie manager. */
- @NonnullAfterInit private CookieManager cookieManager;
-
- /** Cookie prefix. */
- @Nonnull private String cookiePrefix;
-
- /** Lookup strategy for state token value. */
- @NonnullAfterInit private Function<ProfileRequestContext,String> stateTokenLookupStrategy;
-
- /** State token accompanying request. */
- @NonnullBeforeExec private String stateToken;
-
- /** Constructor. */
- public ProcessCorrelationCookie() {
- cookiePrefix = IssueCorrelationCookie.DEFAULT_COOKIE_PREFIX;
- }
-
- /**
- * Sets the {@link CookieManager} to use.
- *
- * @param manager cookie manager instance
- */
- public void setCookieManager(@Nonnull final CookieManager manager) {
- checkSetterPreconditions();
-
- cookieManager = Constraint.isNotNull(manager, "CookieManager cannot be null");
- }
-
- /**
- * Sets the cookie prefix.
- *
- * <p>Defaults to {@link IssueCorrelationCookie#DEFAULT_COOKIE_PREFIX}.</p>
- *
- * @param prefix cookie prefix
- */
- public void setCookiePrefix(@Nonnull @NotEmpty final String prefix) {
- checkSetterPreconditions();
-
- cookiePrefix = Constraint.isNotNull(StringSupport.trimOrNull(prefix), "Cookie prefix cannot be null or empty");
- }
-
- /**
- * Sets the lookup strategy for obtaining the request message's ID.
- *
- * @param strategy lookup strategy
- */
- public void setStateTokenLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
- checkSetterPreconditions();
-
- stateTokenLookupStrategy = Constraint.isNotNull(strategy, "Request ID lookup strategy cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (cookieManager == null) {
- throw new ComponentInitializationException("CookieManager cannot be null");
- } else if (stateTokenLookupStrategy == null) {
- throw new ComponentInitializationException("State token lookup strategy cannot be null");
- }
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- if (!super.doPreExecute(profileRequestContext)) {
- return false;
- }
-
- stateToken = stateTokenLookupStrategy.apply(profileRequestContext);
- if (stateToken == null) {
- log.debug("{} No state token found in request, skipping correlation cookie processing", getLogPrefix());
- return false;
- }
-
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- ensureOutputObjects();
- final AgentRequestContext agentRequestContext = ensureAgentRequestContext();
-
- // We do the crazy stuff to accomodate the cookies being read and unset.
- try {
- RemotedHttpServletRequestResponseContext.loadCurrent(agentRequestContext.getRemotedHttpServletRequest(),
- agentRequestContext.getRemotedHttpServletResponse());
-
- final Escaper escaper = UrlEscapers.urlFormParameterEscaper();
- final String cookieName = cookiePrefix + escaper.escape(stateToken);
-
- final String value = cookieManager.getCookieValue(cookieName, null);
- if (value == null) {
- log.debug("{} No correlation cookie found for state token '{}'", getLogPrefix(), stateToken);
- return;
- }
-
- cookieManager.unsetCookie(cookieName);
-
- final String decoded = URISupport.doURLDecode(value);
-
- log.debug("{} Extracted request ID '{}' for state token '{}'", getLogPrefix(), decoded, stateToken);
-
- agentRequestContext.ensureSubcontext(TokenConsumerContext.class).setMessageCorrelationID(decoded);
- } finally {
- RemotedHttpServletRequestResponseContext.clearCurrent();
- }
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/RecoverStateData.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/RecoverStateData.java
index 449e6ea..0f3cefa 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/RecoverStateData.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/RecoverStateData.java
@@ -18,6 +18,7 @@ import java.io.IOException;
import java.util.function.Function;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
@@ -32,19 +33,24 @@ import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.sp.context.AgentRequestContext;
import net.shibboleth.sp.context.StateDataContext;
+import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
import net.shibboleth.sp.profile.AbstractApplicationAction;
+import net.shibboleth.sp.profile.SPConstants;
import net.shibboleth.sp.state.StateData;
/**
* Action that maps a state token into a {@link StateData} (or subclass) object.
*
- * <p>The state token comes from a pluggable function but its absence will not result
- * in failure.</p>
+ * <p>The state token comes from a pluggable function.</p>
*
* <p>The token and the recovered object will be set into a {@link StateDataContext}
* created by a pluggable strategy.</p>
*
+ * <p>Errors resulting from the absence of a state token or a failure to recover associated state
+ * may be ignored or result in an {@link EventIds#IO_ERROR} event. For example, if the protocol supports
+ * unsolicited requests, a failure to retrieve prior state may be expected and should be ignored.</p>
+ *
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#INVALID_PROFILE_CTX}
* @event {@link EventIds#IO_ERROR}
@@ -54,7 +60,7 @@ import net.shibboleth.sp.state.StateData;
public class RecoverStateData extends AbstractApplicationAction {
/** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(RecoverStateData.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(RecoverStateData.class);
/** Strategy used to create the {@link StateDataContext} to populate. */
@Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextCreationStrategy;
@@ -62,6 +68,9 @@ public class RecoverStateData extends AbstractApplicationAction {
/** Lookup strategy for state token. */
@NonnullAfterInit private Function<ProfileRequestContext,String> stateTokenLookupStrategy;
+ /** Whether any failure to recover previously stored state should result in a fatal event. */
+ private boolean errorFatal;
+
/** Specific class to be recovered. */
@Nonnull private Class<? extends StateData> stateDataClass;
@@ -72,6 +81,19 @@ public class RecoverStateData extends AbstractApplicationAction {
public RecoverStateData() {
stateDataContextCreationStrategy = new ChildContextLookup<>(StateDataContext.class, true);
stateDataClass = StateData.class;
+ errorFatal = false;
+ }
+
+ /**
+ * Sets the strategy used to create the {@link StateDataContext}.
+ *
+ * @param strategy creation strategy
+ */
+ public void setStateDataContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,StateDataContext> strategy) {
+ checkSetterPreconditions();
+ stateDataContextCreationStrategy =
+ Constraint.isNotNull(strategy, "StateDataContext creation strategy cannot be null");
}
/**
@@ -85,15 +107,16 @@ public class RecoverStateData extends AbstractApplicationAction {
}
/**
- * Sets the strategy used to create the {@link StateDataContext}.
+ * Sets whether any failure to recover previously stored state should result in a fatal event.
*
- * @param strategy creation strategy
+ * <p>Defaults to false.</p>
+ *
+ * @param flag flag to set
*/
- public void setStateDataContextCreationStrategy(
- @Nonnull final Function<ProfileRequestContext,StateDataContext> strategy) {
+ public void setErrorFatal(final boolean flag) {
checkSetterPreconditions();
- stateDataContextCreationStrategy =
- Constraint.isNotNull(strategy, "StateDataContext creation strategy cannot be null");
+
+ errorFatal = flag;
}
/**
@@ -112,7 +135,7 @@ public class RecoverStateData extends AbstractApplicationAction {
super.doInitialize();
if (stateTokenLookupStrategy == null) {
- throw new ComponentInitializationException("State token lookup strategy cannot be null");
+ stateTokenLookupStrategy = new DefaultStateTokenLookupStrategy();
}
}
@@ -125,8 +148,14 @@ public class RecoverStateData extends AbstractApplicationAction {
stateToken = stateTokenLookupStrategy.apply(profileRequestContext);
if (stateToken == null) {
- log.debug("{} No state token returned from lookup strategy, nothing to do", getLogPrefix());
- return false;
+ if (!errorFatal) {
+ log.debug("{} No state token returned from lookup strategy, nothing to do", getLogPrefix());
+ return false;
+ } else {
+ log.warn("{} No state token returned from lookup strategy, fatal error", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ return false;
+ }
}
return true;
@@ -162,12 +191,39 @@ public class RecoverStateData extends AbstractApplicationAction {
log.debug("{} State data recovered from token: {}", getLogPrefix(), stateToken);
} else {
log.warn("{} Unable to recover data from state token", getLogPrefix());
+ if (errorFatal) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ }
}
} catch (final IOException e) {
log.warn("{} Exception recovering data from state token", getLogPrefix(), e);
+ if (errorFatal) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ }
} finally {
RemotedHttpServletRequestResponseContext.clearCurrent();
}
}
+
+ /**
+ * Default lookup strategy locates state token from Agent request.
+ */
+ public static final class DefaultStateTokenLookupStrategy implements Function<ProfileRequestContext,String> {
+
+ /** {@inheritDoc} */
+ @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+ if (input != null) {
+ final AgentRequestContext agentRequestContext = input.getSubcontext(AgentRequestContext.class);
+ if (agentRequestContext != null) {
+ final DDF inputMessage = agentRequestContext.getInput();
+ if (inputMessage != null) {
+ return inputMessage.getmember(SPConstants.STATE).string();
+ }
+ }
+ }
+ return null;
+ }
+
+ }
}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/CookieStateManager.java b/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/CookieStateManager.java
index 5753412..d1fef30 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/CookieStateManager.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/CookieStateManager.java
@@ -16,8 +16,6 @@ package net.shibboleth.sp.state.impl;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import javax.annotation.Nonnull;
@@ -25,6 +23,8 @@ import javax.annotation.Nullable;
import org.slf4j.Logger;
+import com.google.common.net.UrlEscapers;
+
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.codec.Base64Support;
@@ -33,11 +33,9 @@ import net.shibboleth.shared.codec.EncodingException;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.net.CookieManager;
+import net.shibboleth.shared.net.URISupport;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.shared.security.IdentifierGenerationStrategy;
-import net.shibboleth.shared.security.IdentifierGenerationStrategy.ProviderType;
-import net.shibboleth.shared.security.RandomIdentifierParameterSpec;
import net.shibboleth.sp.Agent;
import net.shibboleth.sp.Application;
import net.shibboleth.sp.state.AbstractStateManager;
@@ -92,16 +90,6 @@ public class CookieStateManager extends AbstractStateManager {
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
-
- if (getIdentifierGenerationStrategy() == null) {
- final RandomIdentifierParameterSpec spec = new RandomIdentifierParameterSpec(null, 6, null);
- try {
- setIdentifierGenerationStrategy(IdentifierGenerationStrategy.getInstance(ProviderType.SECURE, spec));
- } catch (final InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
- throw new ComponentInitializationException(e);
- }
- }
-
super.doInitialize();
if (cookieManager == null) {
@@ -112,7 +100,7 @@ public class CookieStateManager extends AbstractStateManager {
/** {@inheritDoc} */
@Override
@Nonnull protected String doPreserve(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final String data) throws IOException {
+ @Nonnull final String data, final boolean sealed) throws IOException {
cookieManager.purgeStaleCookies(cookiePrefix);
@@ -121,11 +109,17 @@ public class CookieStateManager extends AbstractStateManager {
final String key = Long.toString(ts.toEpochMilli()) + '_' + generateToken();
final String name = getCookieName(application, key);
- try {
- cookieManager.addCookie(name, Base64Support.encodeURLSafe(data.getBytes(StandardCharsets.UTF_8)),
+
+ if (sealed) {
+ cookieManager.addCookie(name, UrlEscapers.urlFormParameterEscaper().escape(data),
(int) getExpiration().toSeconds());
- } catch (final EncodingException e) {
- throw new IOException(e);
+ } else {
+ try {
+ cookieManager.addCookie(name, Base64Support.encodeURLSafe(data.getBytes(StandardCharsets.UTF_8)),
+ (int) getExpiration().toSeconds());
+ } catch (final EncodingException e) {
+ throw new IOException(e);
+ }
}
log.trace("Created state token mapping from '{}' to value '{}'", name, data);
@@ -136,20 +130,26 @@ public class CookieStateManager extends AbstractStateManager {
/** {@inheritDoc} */
@Override
@Nullable protected String doRecover(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull @NotEmpty final String stateToken) throws IOException {
+ @Nonnull @NotEmpty final String stateToken, final boolean sealed) throws IOException {
final String cookieName = getCookieName(application, stateToken);
final String cookieValue = cookieManager.getCookieValue(cookieName, null);
if (cookieValue != null) {
cookieManager.unsetCookie(cookieName);
- try {
- final String decoded = new String(Base64Support.decodeURLSafe(cookieValue), StandardCharsets.UTF_8);
- log.trace("Recovered state token mapping from '{}' to value '{}'", stateToken, decoded);
- return decoded;
- } catch (final DecodingException e) {
- throw new IOException(e);
+
+ final String decoded;
+ if (sealed) {
+ decoded = URISupport.doURLDecode(cookieValue);
+ } else {
+ try {
+ decoded = new String(Base64Support.decodeURLSafe(cookieValue), StandardCharsets.UTF_8);
+ } catch (final DecodingException e) {
+ throw new IOException(e);
+ }
}
+ log.trace("Recovered state token mapping from '{}' to value '{}'", stateToken, decoded);
+ return decoded;
}
log.warn("No cookie found matching state token: '{}'", stateToken);
@@ -171,7 +171,7 @@ public class CookieStateManager extends AbstractStateManager {
final StringBuilder builder = new StringBuilder(cookiePrefix);
builder.append('_')
- .append(application.getId())
+ .append(application.getApplicationId())
.append('_')
.append(uniquePortion);
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/StorageServiceStateManager.java b/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/StorageServiceStateManager.java
index 59858f7..96ba264 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/StorageServiceStateManager.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/StorageServiceStateManager.java
@@ -121,7 +121,7 @@ public class StorageServiceStateManager extends AbstractStateManager {
/** {@inheritDoc} */
@Override
@Nonnull protected String doPreserve(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final String data) throws IOException {
+ @Nonnull final String data, final boolean sealed) throws IOException {
final String context = getContext(agent, application);
final String key = generateToken();
@@ -130,7 +130,7 @@ public class StorageServiceStateManager extends AbstractStateManager {
String encoded;
try {
- encoded = getDataSealer() != null ? data : Base64Support.encode(data.getBytes(StandardCharsets.UTF_8), false);
+ encoded = sealed ? data : Base64Support.encode(data.getBytes(StandardCharsets.UTF_8), false);
} catch (final EncodingException e) {
throw new IOException(e);
}
@@ -164,7 +164,7 @@ public class StorageServiceStateManager extends AbstractStateManager {
/** {@inheritDoc} */
@Override
@Nullable protected String doRecover(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull @NotEmpty final String stateToken) throws IOException {
+ @Nonnull @NotEmpty final String stateToken, final boolean sealed) throws IOException {
final String key;
final String context = getContext(agent, application);
@@ -194,7 +194,7 @@ public class StorageServiceStateManager extends AbstractStateManager {
log.warn("Unable to delete state record ('{}', '{}') from storage", context, key, e);
}
try {
- final String decoded = getDataSealer() != null ? record.getValue() :
+ final String decoded = sealed ? record.getValue() :
new String(Base64Support.decode(record.getValue()), StandardCharsets.UTF_8);
log.trace("Recovered state record ('{}', '{}') with value '{}'", context, key, decoded);
return decoded;
@@ -236,7 +236,7 @@ public class StorageServiceStateManager extends AbstractStateManager {
final StringBuilder builder = new StringBuilder(cookiePrefix);
builder.append('_')
- .append(application.getId())
+ .append(application.getApplicationId())
.append('_')
.append(uniquePortion);
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/InitializeAgentRequestContextTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/InitializeAgentRequestContextTest.java
index ea93a8a..5a9c685 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/InitializeAgentRequestContextTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/InitializeAgentRequestContextTest.java
@@ -34,7 +34,6 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.testing.MockReloadableService;
import net.shibboleth.sp.context.AgentRequestContext;
import net.shibboleth.sp.impl.BasicAgent;
-import net.shibboleth.sp.impl.PassthroughStateTokenManager;
import net.shibboleth.sp.impl.ProtocolSupportServiceManager;
import net.shibboleth.sp.state.impl.StorageServiceStateManager;
import net.shibboleth.sp.testing.MockAgentResolver;
@@ -62,21 +61,16 @@ public class InitializeAgentRequestContextTest {
agent.setAttributeFilter(new MockReloadableService<>(null));
agent.setAttributeTranscoderRegistry(new MockReloadableService<>(null));
- final PassthroughStateTokenManager manager = new PassthroughStateTokenManager();
- manager.setId("test");
- manager.initialize();
- agent.setStateTokenManager(manager);
-
final MemoryStorageService storage = new MemoryStorageService();
storage.setId("test");
storage.initialize();
- final StorageServiceStateManager manager2 = new StorageServiceStateManager();
- manager2.setId("test");
- manager2.setObjectMapper(new JsonMapper());
- manager2.setStorageService(storage);
- manager2.initialize();
- agent.setStateManager(manager2);
+ final StorageServiceStateManager manager = new StorageServiceStateManager();
+ manager.setId("test");
+ manager.setObjectMapper(new JsonMapper());
+ manager.setStorageService(storage);
+ manager.initialize();
+ agent.setStateManager(manager);
agent.initialize();
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/impl/CookieStateTokenManagerTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/impl/CookieStateTokenManagerTest.java
deleted file mode 100644
index 2952d7a..0000000
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/impl/CookieStateTokenManagerTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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.impl;
-
-import java.io.IOException;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.testng.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import jakarta.servlet.http.Cookie;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.shared.net.CookieManager.SameSiteValue;
-import net.shibboleth.shared.primitive.NonnullSupplier;
-import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
-
-/**
- * Unit tests for {@link CookieStateTokenManager}.
- */
- at SuppressWarnings("javadoc")
-public class CookieStateTokenManagerTest extends BaseApplicationActionTest {
-
- private CookieManager cookieManager;
- private CookieStateTokenManager stateManager;
-
- private MockHttpServletRequest request;
- private MockHttpServletResponse response;
-
- @BeforeClass
- public void setUp() throws ComponentInitializationException {
-
- cookieManager = new CookieManager();
- cookieManager.setCookiePath("/");
- cookieManager.setSameSite(SameSiteValue.None);
- cookieManager.setCookieLimit(10);
- cookieManager.setHttpServletRequestSupplier(new NonnullSupplier<HttpServletRequest>() {
- @Nonnull public HttpServletRequest get() {
- assert request != null;
- return request;
- }
- });
- cookieManager.setHttpServletResponseSupplier(new NonnullSupplier<HttpServletResponse>() {
- @Nonnull public HttpServletResponse get() {
- assert response != null;
- return response;
- }
- });
- cookieManager.initialize();
-
- stateManager = new CookieStateTokenManager();
- stateManager.setId("test");
- stateManager.setCookieManager(cookieManager);
-
- stateManager.initialize();
- }
-
- @AfterClass
- public void tearDown() {
- stateManager.destroy();
- cookieManager.destroy();
- }
-
- @BeforeMethod
- public void beforeMethod() throws ComponentInitializationException {
- super.beforeMethod();
-
- request = new MockHttpServletRequest();
- response = new MockHttpServletResponse();
- }
-
- @Test
- public void testMissing() throws IOException {
- request.setCookies(new Cookie(getCookieName(), "foo"));
-
- Assert.assertNull(stateManager.recoverFromStateToken(agent, application, "foo"));
- }
-
- @Test
- public void testPurge() throws IOException, InterruptedException {
-
- final List<Cookie> cookies = new ArrayList<>(12);
- for (int i = 0; i < 12; ++i) {
- cookies.add(new Cookie(getCookieName(), "foo" + i));
- Thread.sleep(250);
- }
- request.setCookies(cookies.toArray(new Cookie[12]));
-
- final String token = stateManager.preserveToStateToken(agent, application, "foo".getBytes());
- assert token != null;
-
- final Cookie[] respCookies = response.getCookies();
- Assert.assertEquals(respCookies.length, 3);
- Assert.assertEquals(respCookies[0].getMaxAge(), 0);
- Assert.assertEquals(respCookies[1].getMaxAge(), 0);
- Assert.assertEquals(respCookies[2].getMaxAge(), stateManager.getExpiration().toSeconds());
- }
-
- @Test
- public void testMapRecover() throws IOException {
-
- final String token = stateManager.preserveToStateToken(agent, application, "foo".getBytes());
- assert token != null;
-
- // Move token set on response to request.
- request = new MockHttpServletRequest();
- request.setCookies(response.getCookies());
- response = new MockHttpServletResponse();
-
- final byte[] original = stateManager.recoverFromStateToken(agent, application, token);
- Assert.assertEquals(original, "foo".getBytes());
-
- // Check that old token is unset.
- final Cookie[] cookies = response.getCookies();
- Assert.assertEquals(cookies.length, 1);
- Assert.assertEquals(cookies[0].getName(), CookieStateTokenManager.DEFAULT_PREFIX + '_' + "test" + '_' + token);
- Assert.assertEquals(cookies[0].getValue(), null);
- Assert.assertEquals(cookies[0].getMaxAge(), 0);
- Assert.assertEquals(cookies[0].getAttribute("SameSite"), SameSiteValue.None.getValue());
- }
-
- @Nonnull private String getCookieName() {
- final Instant now = Instant.now();
- final String rand = stateManager.getIdentifierGenerationStrategy().generateIdentifier(false);
- return stateManager.getCookieName(application, now.toEpochMilli() + '_' + rand);
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/impl/PassthroughStateTokenManagerTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/impl/PassthroughStateTokenManagerTest.java
deleted file mode 100644
index c78bfcb..0000000
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/impl/PassthroughStateTokenManagerTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.impl;
-
-import java.io.IOException;
-
-import org.testng.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
-
-/**
- * Unit tests for {@link PassthroughStateTokenManager}.
- */
- at SuppressWarnings("javadoc")
-public class PassthroughStateTokenManagerTest extends BaseApplicationActionTest {
-
- private PassthroughStateTokenManager stateManager;
-
- @BeforeClass
- public void setUp() throws ComponentInitializationException {
-
- stateManager = new PassthroughStateTokenManager();
- stateManager.setId("test");
- stateManager.initialize();
- }
-
- @AfterClass
- public void tearDown() {
- stateManager.destroy();
- }
-
- @BeforeMethod
- public void beforeMethod() throws ComponentInitializationException {
- super.beforeMethod();
- }
-
- @Test
- public void testInvalid() throws IOException {
- Assert.assertNull(stateManager.recoverFromStateToken(agent, application, "123"));
- }
-
- @Test
- public void testMapRecover() throws IOException {
-
- final String token = stateManager.preserveToStateToken(agent, application, "foo".getBytes());
- assert token != null;
-
- final byte[] original = stateManager.recoverFromStateToken(agent, application, token);
- Assert.assertEquals(original, "foo".getBytes());
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/impl/StorageServiceStateTokenManagerTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/impl/StorageServiceStateTokenManagerTest.java
deleted file mode 100644
index e9f1a0c..0000000
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/impl/StorageServiceStateTokenManagerTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.impl;
-
-import java.io.IOException;
-import java.time.Duration;
-
-import org.opensaml.storage.impl.MemoryStorageService;
-import org.testng.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
-
-/**
- * Unit tests for {@link StorageServiceStateTokenManager}.
- */
- at SuppressWarnings("javadoc")
-public class StorageServiceStateTokenManagerTest extends BaseApplicationActionTest {
-
- private MemoryStorageService storageService;
- private StorageServiceStateTokenManager stateManager;
-
- @BeforeClass
- public void setUp() throws ComponentInitializationException {
- storageService = new MemoryStorageService();
- storageService.setId("test");
- storageService.setCleanupInterval(Duration.ZERO);
- storageService.initialize();
-
- stateManager = new StorageServiceStateTokenManager();
- stateManager.setStorageService(storageService);
- stateManager.setId("test");
- stateManager.initialize();
- }
-
- @AfterClass
- public void tearDown() {
- stateManager.destroy();
- storageService.destroy();
- }
-
- @BeforeMethod
- public void beforeMethod() throws ComponentInitializationException {
- super.beforeMethod();
- }
-
- @Test
- public void testMissing() throws IOException {
- Assert.assertNull(stateManager.recoverFromStateToken(agent, application, "foo"));
- }
-
- @Test
- public void testMapRecover() throws IOException {
-
- final String token = stateManager.preserveToStateToken(agent, application, "foo".getBytes());
- assert token != null;
-
- final byte[] original = stateManager.recoverFromStateToken(agent, application, token);
- Assert.assertEquals(original, "foo".getBytes());
-
- Assert.assertNull(stateManager.recoverFromStateToken(agent, application, token));
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgentRequestTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgentRequestTest.java
index 91bdae5..4ff1a39 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgentRequestTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgentRequestTest.java
@@ -27,7 +27,6 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.testing.MockReloadableService;
import net.shibboleth.sp.context.AgentRequestContext;
import net.shibboleth.sp.impl.BasicAgent;
-import net.shibboleth.sp.impl.PassthroughStateTokenManager;
import net.shibboleth.sp.impl.ProtocolSupportServiceManager;
import net.shibboleth.sp.state.impl.StorageServiceStateManager;
@@ -57,11 +56,6 @@ public abstract class BaseAgentRequestTest extends OpenSAMLInitBaseTestCase {
agent.setAttributeFilter(new MockReloadableService<>(null));
agent.setAttributeTranscoderRegistry(new MockReloadableService<>(null));
- final PassthroughStateTokenManager manager = new PassthroughStateTokenManager();
- manager.setId("test");
- manager.initialize();
- agent.setStateTokenManager(manager);
-
final MemoryStorageService storage = new MemoryStorageService();
storage.setId("test");
storage.initialize();
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueCorrelationCookieTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueCorrelationCookieTest.java
deleted file mode 100644
index 9a89f89..0000000
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueCorrelationCookieTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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.profile.impl;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.webflow.execution.Event;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-import jakarta.servlet.http.Cookie;
-import net.shibboleth.idp.profile.testing.ActionTestingSupport;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.codec.DecodingException;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.shared.net.CookieManager.SameSiteValue;
-import net.shibboleth.shared.primitive.NonnullSupplier;
-import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.profile.SPConstants;
-
-/**
- * Unit test for {@link IssueCorrelationCookie} action.
- */
- at SuppressWarnings("javadoc")
-public class IssueCorrelationCookieTest extends BaseApplicationActionTest {
-
- @Nonnull @NotEmpty private final static String TEST_STATE = "foo";
- @Nonnull @NotEmpty private final static String TEST_ID = "123456789";
-
- private String requestId;
-
- private DDF input;
- private MockHttpServletRequest request;
- private MockHttpServletResponse response;
-
- private CookieManager cookieManager;
- private IssueCorrelationCookie action;
-
- /**
- * Set up test.
- *
- * @throws ComponentInitializationException
- */
- @BeforeMethod
- public void setUp() throws ComponentInitializationException {
- super.beforeMethod();
-
- request = new MockHttpServletRequest();
- response = new MockHttpServletResponse();
-
- cookieManager = new CookieManager();
- cookieManager.setHttpServletRequestSupplier(NonnullSupplier.of(request));
- cookieManager.setHttpServletResponseSupplier(NonnullSupplier.of(response));
- cookieManager.setCookieLimit(10);
- cookieManager.setSameSite(SameSiteValue.None);
- cookieManager.setMaxAge(-1);
- cookieManager.initialize();
-
- action = new IssueCorrelationCookie();
- action.setCookieManager(cookieManager);
- action.setRequestIDLookupStrategy(new Function<>() {
- public String apply(ProfileRequestContext t) {
- return requestId;
- }
- });
-
- action.setErrorFatal(true);
- action.initialize();
-
- input = new DDF(null).structure();
- arc.setInput(input);
- }
-
- /**
- * Tear down test.
- */
- @AfterMethod
- public void tearDown() {
- action.destroy();
- cookieManager.destroy();
- }
-
- @DataProvider
- Object[][] correlationData() {
- return new Object[][] {
- new Object[] { null, TEST_ID},
- new Object[] { TEST_STATE, null},
- new Object[] { TEST_STATE, TEST_ID},
- };
- }
-
- @Test(dataProvider="correlationData")
- public void testAction(final String state, final String id) {
- evaluateAction(state, id);
- }
-
- private void evaluateAction(final String state, final String id) {
- requestId = id;
- if (state != null) {
- input.addmember(SPConstants.STATE).string(state);
- }
-
- final Event event = action.execute(src);
-
- if (state != null) {
- ActionTestingSupport.assertProceedEvent(event);
- } else {
- ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
- return;
- }
-
- if (id == null) {
- Assert.assertEquals(response.getCookies().length, 0);
- return;
- }
-
- final Cookie cookie = response.getCookie(IssueCorrelationCookie.DEFAULT_COOKIE_PREFIX + state);
- assert cookie != null;
- Assert.assertEquals(cookie.getValue(), requestId);
- Assert.assertEquals(cookie.getMaxAge(), -1);
- Assert.assertEquals(cookie.getAttribute("SameSite"), SameSiteValue.None.getValue());
- }
-
- @Test
- public void testPurge() throws ComponentInitializationException, DecodingException, InterruptedException {
-
- final List<Cookie> cookies = new ArrayList<>(12);
- for (int i = 0; i < 12; ++i) {
- cookies.add(new Cookie(IssueCorrelationCookie.DEFAULT_COOKIE_PREFIX + i, "foo" + i));
- Thread.sleep(250);
- }
- request.setCookies(cookies.toArray(new Cookie[12]));
-
- evaluateAction(TEST_STATE, TEST_ID);
-
- Assert.assertEquals(response.getCookies().length, 3);
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequestTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequestTest.java
index 089ed29..e809910 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequestTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequestTest.java
@@ -33,11 +33,11 @@ import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.profile.relyingparty.BasicRelyingPartyConfiguration;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
import net.shibboleth.sp.messaging.impl.RemotedlHttpServletResponseSupplier;
import net.shibboleth.sp.profile.InitiatorConstants;
-import net.shibboleth.sp.profile.SPConstants;
/**
* Unit test for {@link IssueDiscoveryRequest} action.
@@ -155,10 +155,12 @@ public class IssueDiscoveryRequestTest extends BaseApplicationActionTest {
@Test
public void testState() throws ComponentInitializationException {
final DDF input = new DDF(null).structure();
- input.addmember(SPConstants.STATE).string("foo bar");
input.addmember(InitiatorConstants.DISCOVERY_RETURN_URL).string(escaper.escape("https://sp.example.org/handler?DS=1"));
arc.setInput(input);
+ // Populate state for action.
+ prc.ensureSubcontext(StateDataContext.class).setStateToken("foo bar");
+
final Event event = action.execute(src);
ActionTestingSupport.assertProceedEvent(event);
@@ -170,15 +172,16 @@ public class IssueDiscoveryRequestTest extends BaseApplicationActionTest {
Assert.assertEquals(redirect, buildDiscoveryURL(TEST_DISCOVERY_URL, TEST_RESPONSE_URL, "foo bar", false));
}
-
@Test
public void testPassive() throws ComponentInitializationException {
final DDF input = new DDF(null).structure();
- input.addmember(SPConstants.STATE).string("foo bar");
input.addmember(InitiatorConstants.DISCOVERY_RETURN_URL).string(escaper.escape("https://sp.example.org/handler?DS=1"));
input.addmember("prompt").string("none");
arc.setInput(input);
+ // Populate state for action.
+ prc.ensureSubcontext(StateDataContext.class).setStateToken("foo bar");
+
final Event event = action.execute(src);
ActionTestingSupport.assertProceedEvent(event);
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/MapResourceToStateTokenTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/MapResourceToStateTokenTest.java
deleted file mode 100644
index a38e959..0000000
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/MapResourceToStateTokenTest.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * 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.profile.impl;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.time.Duration;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.storage.impl.MemoryStorageService;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-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.profile.testing.ActionTestingSupport;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.shared.codec.DecodingException;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.shared.primitive.NonnullSupplier;
-import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.impl.CookieStateTokenManager;
-import net.shibboleth.sp.impl.PassthroughStateTokenManager;
-import net.shibboleth.sp.impl.StorageServiceStateTokenManager;
-import net.shibboleth.sp.profile.SPConstants;
-
-/**
- * Unit test for {@link MapResourceToStateToken} action.
- */
- at SuppressWarnings("javadoc")
-public class MapResourceToStateTokenTest extends BaseApplicationActionTest {
-
- /** Test request URL. */
- @Nonnull @NotEmpty private final static String TEST_URL = "https://sp.example.org/cgi-bin/test.cgi";
-
- /** Test query string. */
- @Nonnull @NotEmpty private final static String TEST_QUERY = TEST_URL + "?foo=bar%20baz&frobnitz=zorkmid";
-
- private MapResourceToStateToken action;
-
- /**
- * Set up test.
- *
- * @throws ComponentInitializationException
- */
- @BeforeMethod
- public void setUp() throws ComponentInitializationException {
- super.beforeMethod();
-
- action = new MapResourceToStateToken();
- action.initialize();
- }
-
- /**
- * Tear down test.
- */
- @AfterMethod
- public void tearDown() {
- action.destroy();
- }
-
- @Test
- public void testNoTarget() {
- final DDF input = new DDF(null).structure();
- arc.setInput(input);
-
- final Event event = action.execute(src);
- ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
- }
-
- @Test
- public void testStateExists() {
- final DDF input = new DDF(null).structure();
- input.addmember(SPConstants.STATE).string("foo");
- arc.setInput(input);
-
- final Event event = action.execute(src);
- ActionTestingSupport.assertProceedEvent(event);
-
- Assert.assertEquals(input.getmember(SPConstants.STATE).string(), "foo");
- }
-
- @Test
- public void testPassthroughURL() throws ComponentInitializationException, DecodingException {
-
- final PassthroughStateTokenManager manager = new PassthroughStateTokenManager();
- manager.setId("test");
- manager.initialize();
-
- application.setStateTokenManager(manager);
- application.initialize();
-
- final DDF input = new DDF(null).structure();
- input.addmember(SPConstants.TARGET).unsafe_string(TEST_URL.getBytes(StandardCharsets.UTF_8));
- arc.setInput(input);
-
- final Event event = action.execute(src);
- ActionTestingSupport.assertProceedEvent(event);
-
- final String token = input.getmember(SPConstants.STATE).string();
- assert token != null;
-
- Assert.assertEquals(Base64Support.decodeURLSafe(token), TEST_URL.getBytes());
- }
-
- @Test
- public void testPassthroughURLAndQuery() throws ComponentInitializationException, DecodingException {
-
- final PassthroughStateTokenManager manager = new PassthroughStateTokenManager();
- manager.setId("test");
- manager.initialize();
-
- application.setStateTokenManager(manager);
- application.initialize();
-
- final DDF input = new DDF(null).structure();
- input.addmember(SPConstants.TARGET).unsafe_string(TEST_QUERY.getBytes());
- arc.setInput(input);
-
- final Event event = action.execute(src);
- ActionTestingSupport.assertProceedEvent(event);
-
- final String token = input.getmember(SPConstants.STATE).string();
- assert token != null;
-
- Assert.assertEquals(Base64Support.decodeURLSafe(token), TEST_QUERY.getBytes(StandardCharsets.UTF_8));
- }
-
- @Test
- public void testStorageURL() throws ComponentInitializationException, DecodingException, IOException {
-
- final MemoryStorageService storage = new MemoryStorageService();
- storage.setId("test");
- storage.setCleanupInterval(Duration.ZERO);
- storage.initialize();
-
- final StorageServiceStateTokenManager manager = new StorageServiceStateTokenManager();
- manager.setId("test");
- manager.setStorageService(storage);
- manager.initialize();
-
- application.setStateTokenManager(manager);
- application.initialize();
-
- final DDF input = new DDF(null).structure();
- input.addmember(SPConstants.TARGET).unsafe_string(TEST_URL.getBytes(StandardCharsets.UTF_8));
- arc.setInput(input);
-
- final Event event = action.execute(src);
- ActionTestingSupport.assertProceedEvent(event);
-
- final String token = input.getmember(SPConstants.STATE).string();
- assert token != null;
-
- final byte[] value = manager.recoverFromStateToken(agent, application, token);
- Assert.assertEquals(value, TEST_URL.getBytes(StandardCharsets.UTF_8));
- }
-
- @Test
- public void testCookieURL() throws ComponentInitializationException, DecodingException, IOException {
-
- final MockHttpServletRequest request = new MockHttpServletRequest();
- final MockHttpServletResponse response = new MockHttpServletResponse();
-
- final CookieManager cookieManager = new CookieManager();
- cookieManager.setHttpServletRequestSupplier(NonnullSupplier.of(request));
- cookieManager.setHttpServletResponseSupplier(NonnullSupplier.of(response));
- cookieManager.initialize();
-
- final CookieStateTokenManager manager = new CookieStateTokenManager();
- manager.setId("test");
- manager.setCookieManager(cookieManager);
- manager.initialize();
-
- application.setStateTokenManager(manager);
- application.initialize();
-
- final DDF input = new DDF(null).structure();
- input.addmember(SPConstants.TARGET).unsafe_string(TEST_URL.getBytes(StandardCharsets.UTF_8));
- arc.setInput(input);
-
- final Event event = action.execute(src);
- ActionTestingSupport.assertProceedEvent(event);
-
- final String token = input.getmember(SPConstants.STATE).string();
- assert token != null;
-
- request.setCookies(response.getCookies());
-
- final byte[] value = manager.recoverFromStateToken(agent, application, token);
- Assert.assertEquals(value, TEST_URL.getBytes(StandardCharsets.UTF_8));
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/PreservePostDataTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/PreservePostDataTest.java
index 2fd2828..a9fb9cf 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/PreservePostDataTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/PreservePostDataTest.java
@@ -33,12 +33,16 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.net.CookieManager;
import net.shibboleth.shared.net.CookieManager.SameSiteValue;
import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.profile.SPConstants;
@@ -75,8 +79,16 @@ public class PreservePostDataTest extends BaseApplicationActionTest {
response = new MockHttpServletResponse();
cookieManager = new CookieManager();
- cookieManager.setHttpServletRequestSupplier(NonnullSupplier.of(request));
- cookieManager.setHttpServletResponseSupplier(NonnullSupplier.of(response));
+ cookieManager.setHttpServletRequestSupplier(new NonnullSupplier<HttpServletRequest>() {
+ @Nonnull public HttpServletRequest get() {
+ return request;
+ }
+ });
+ cookieManager.setHttpServletResponseSupplier(new NonnullSupplier<HttpServletResponse>() {
+ @Nonnull public HttpServletResponse get() {
+ return response;
+ }
+ });
cookieManager.setCookieLimit(10);
cookieManager.setSameSite(SameSiteValue.None);
cookieManager.setMaxAge(-1);
@@ -94,7 +106,6 @@ public class PreservePostDataTest extends BaseApplicationActionTest {
action.initialize();
input = new DDF(null).structure();
- input.addmember(SPConstants.STATE).string(TEST_STATE);
final DDF http = input.addmember(RemotedHttpServletRequest.STRUCTURE_NAME);
http.addmember(RemotedHttpServletRequest.CONTENT_TYPE).string("application/x-www-form-urlencoded");
http.addmember(RemotedHttpServletRequest.BODY).unsafe_string(TEST_DATA.getBytes());
@@ -146,18 +157,20 @@ public class PreservePostDataTest extends BaseApplicationActionTest {
@Test
public void testNoStateToken() {
- input.addmember(SPConstants.STATE).remove();
final Event event = action.execute(src);
ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
}
@Test
public void testSuccess() throws IOException {
+
+ prc.ensureSubcontext(StateDataContext.class).setStateToken(TEST_STATE);
+
final Event event = action.execute(src);
ActionTestingSupport.assertProceedEvent(event);
Assert.assertEquals(response.getCookies().length, 1);
- final Cookie cookie = response.getCookie(PreservePostData.DEFAULT_COOKIE_PREFIX + TEST_STATE);
+ final Cookie cookie = response.getCookie(action.getCookieName(TEST_STATE));
assert cookie != null;
Assert.assertEquals(cookie.getMaxAge(), -1);
Assert.assertEquals(cookie.getAttribute("SameSite"), SameSiteValue.None.getValue());
@@ -172,18 +185,63 @@ public class PreservePostDataTest extends BaseApplicationActionTest {
@Test
public void testPurge() throws ComponentInitializationException, InterruptedException {
-
+
final List<Cookie> cookies = new ArrayList<>(12);
for (int i = 0; i < 12; ++i) {
- cookies.add(new Cookie(PreservePostData.DEFAULT_COOKIE_PREFIX + i, "foo" + i));
+ cookies.add(new Cookie(PreservePostData.DEFAULT_COOKIE_PREFIX + '_' + i, "foo" + i));
Thread.sleep(250);
}
request.setCookies(cookies.toArray(new Cookie[12]));
+ prc.ensureSubcontext(StateDataContext.class).setStateToken(TEST_STATE);
+
final Event event = action.execute(src);
ActionTestingSupport.assertProceedEvent(event);
Assert.assertEquals(response.getCookies().length, 3);
}
+ @Test
+ public void testPreviousRecovery() throws IOException {
+
+ // First run with inline state to preserve data.
+ testSuccess();
+
+ Cookie cookie = response.getCookie(action.getCookieName(TEST_STATE));
+ assert cookie != null;
+ Assert.assertEquals(cookie.getMaxAge(), -1);
+
+ final String originalKey = cookie.getValue();
+
+ // Now re-run with that state supplied via input message and mutate new state.
+
+ Constraint.isNotNull(arc.getInput(), "Input was null").addmember(SPConstants.STATE).string(TEST_STATE);
+ Constraint.isNotNull(arc.getInput(), "Input was null").getmember(RemotedHttpServletRequest.STRUCTURE_NAME).destroy();
+ prc.ensureSubcontext(StateDataContext.class).setStateToken(TEST_STATE + "2");
+
+ // Transfer cookies from response to new request.
+ request = new MockHttpServletRequest();
+ request.setCookies(response.getCookies());
+ response = new MockHttpServletResponse();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ Assert.assertEquals(response.getCookies().length, 2);
+
+ cookie = response.getCookie(action.getCookieName(TEST_STATE + "2"));
+ assert cookie != null;
+ Assert.assertEquals(cookie.getMaxAge(), -1);
+ Assert.assertEquals(cookie.getAttribute("SameSite"), SameSiteValue.None.getValue());
+
+ final String key = cookie.getValue();
+
+ final StorageRecord<String> record = storageService.read(agent.getId() + ".PostData", key);
+ assert record != null;
+ Assert.assertEquals(record.getVersion(), 1);
+ Assert.assertEquals(record.getValue(), TEST_DATA);
+
+ // Make sure original record is gone.
+ Assert.assertNull(storageService.read(agent.getId() + ".PostData", originalKey));
+ }
+
}
\ No newline at end of file
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/ProcessCorrelationCookieTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/ProcessCorrelationCookieTest.java
deleted file mode 100644
index 9c24163..0000000
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/ProcessCorrelationCookieTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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.profile.impl;
-
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.webflow.execution.Event;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-import com.google.common.escape.Escaper;
-import com.google.common.net.UrlEscapers;
-
-import jakarta.servlet.http.Cookie;
-import net.shibboleth.idp.profile.testing.ActionTestingSupport;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.shared.net.CookieManager.SameSiteValue;
-import net.shibboleth.shared.primitive.NonnullSupplier;
-import net.shibboleth.sp.context.TokenConsumerContext;
-import net.shibboleth.sp.ddf.DDF;
-
-/**
- * Unit test for {@link ProcessCorrelationCookie} action.
- */
- at SuppressWarnings("javadoc")
-public class ProcessCorrelationCookieTest extends BaseApplicationActionTest {
-
- @Nonnull @NotEmpty private final static String TEST_STATE = "foo";
- @Nonnull @NotEmpty private final static String TEST_ID = "123456789";
-
- private String state;
-
- private DDF input;
- private MockHttpServletRequest request;
- private MockHttpServletResponse response;
-
- private CookieManager cookieManager;
- private ProcessCorrelationCookie action;
-
- /**
- * Set up test.
- *
- * @throws ComponentInitializationException
- */
- @BeforeMethod
- public void setUp() throws ComponentInitializationException {
- super.beforeMethod();
-
- request = new MockHttpServletRequest();
- response = new MockHttpServletResponse();
-
- cookieManager = new CookieManager();
- cookieManager.setHttpServletRequestSupplier(NonnullSupplier.of(request));
- cookieManager.setHttpServletResponseSupplier(NonnullSupplier.of(response));
- cookieManager.setCookieLimit(10);
- cookieManager.setSameSite(SameSiteValue.None);
- cookieManager.setMaxAge(-1);
- cookieManager.initialize();
-
- action = new ProcessCorrelationCookie();
- action.setCookieManager(cookieManager);
- action.setStateTokenLookupStrategy(new Function<>() {
- public String apply(ProfileRequestContext t) {
- return state;
- }
- });
- action.initialize();
- }
-
- /**
- * Tear down test.
- */
- @AfterMethod
- public void tearDown() {
- action.destroy();
- cookieManager.destroy();
- }
-
- @DataProvider
- Object[][] correlationData() {
- return new Object[][] {
- new Object[] {false, null, null},
- new Object[] {false, TEST_STATE, null},
- new Object[] {true, null, null},
- new Object[] {true, TEST_STATE, TEST_ID},
- };
- }
-
- @Test(dataProvider="correlationData")
- public void testAction(final Boolean createCookie, final String token, final String id) {
- state = token;
-
- if (createCookie && token != null) {
- final Escaper escaper = UrlEscapers.urlFormParameterEscaper();
- final Cookie cookie = new Cookie(IssueCorrelationCookie.DEFAULT_COOKIE_PREFIX + escaper.escape(token), id);
- request.setCookies(cookie);
- }
-
- final Event event = action.execute(src);
- ActionTestingSupport.assertProceedEvent(event);
-
- Assert.assertEquals(response.getCookies().length, createCookie && token != null ? 1 : 0);
-
- final TokenConsumerContext tcc = arc.getSubcontext(TokenConsumerContext.class);
- if (id != null) {
- assert tcc != null;
- Assert.assertEquals(tcc.getMessageCorrelationID(), id);
- } else {
- Assert.assertNull(tcc);
- }
- }
-
-}
\ No newline at end of file
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/CookieStateManagerTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/CookieStateManagerTest.java
index 8689881..9352e26 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/CookieStateManagerTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/CookieStateManagerTest.java
@@ -22,11 +22,11 @@ import java.util.List;
import javax.annotation.Nonnull;
+import org.springframework.core.io.ClassPathResource;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -41,8 +41,12 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.net.CookieManager;
import net.shibboleth.shared.net.CookieManager.SameSiteValue;
import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.shared.resource.Resource;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.impl.BasicKeystoreKeyStrategy;
import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
import net.shibboleth.sp.state.StateData;
+import net.shibboleth.sp.testing.TestResourceConverter;
/**
* Unit tests for {@link CookieStateManager}.
@@ -57,15 +61,17 @@ public class CookieStateManagerTest extends BaseApplicationActionTest {
// Unicode character at the end of that filename...
@Nonnull @NotEmpty private static final String TEST_RESOURCE = "https://sp.example.org/secure/foo☯.cgi";
+ private Resource keystoreResource;
+ private Resource versionResource;
+
+ private DataSealer sealer;
private CookieManager cookieManager;
private CookieStateManager stateManager;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
- @BeforeClass
- public void setUp() throws ComponentInitializationException {
-
+ public void setUp(final boolean sealed) throws ComponentInitializationException {
cookieManager = new CookieManager();
cookieManager.setCookiePath("/");
cookieManager.setSameSite(SameSiteValue.None);
@@ -83,30 +89,57 @@ public class CookieStateManagerTest extends BaseApplicationActionTest {
}
});
cookieManager.initialize();
-
+
stateManager = new CookieStateManager();
stateManager.setId("test");
+ if (sealed) {
+ ClassPathResource resource =
+ new ClassPathResource("/net/shibboleth/sp/profile/impl/SealerKeyStore.jks");
+ Assert.assertTrue(resource.exists());
+ keystoreResource = TestResourceConverter.of(resource);
+
+ resource =
+ new ClassPathResource("/net/shibboleth/sp/profile/impl/SealerKeyStore.kver");
+ Assert.assertTrue(resource.exists());
+ versionResource = TestResourceConverter.of(resource);
+
+ final BasicKeystoreKeyStrategy strategy = new BasicKeystoreKeyStrategy();
+ strategy.setKeyAlias("secret");
+ strategy.setKeyPassword("kpassword");
+ strategy.setKeystorePassword("password");
+ strategy.setKeystoreResource(keystoreResource);
+ strategy.setKeyVersionResource(versionResource);
+ strategy.initialize();
+
+ final DataSealer sealer = new DataSealer();
+ sealer.setKeyStrategy(strategy);
+ sealer.initialize();
+
+ stateManager.setDataSealer(sealer);
+ }
+
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
- stateManager.setObjectMapper(mapper);
-
+ stateManager.setObjectMapper(mapper);
stateManager.setHttpServletRequestSupplier(new NonnullSupplier<HttpServletRequest>() {
@Nonnull public HttpServletRequest get() {
assert request != null;
return request;
}
});
-
stateManager.setCookieManager(cookieManager);
-
stateManager.initialize();
}
-
+
@AfterClass
public void tearDown() {
stateManager.destroy();
cookieManager.destroy();
+ if (sealer != null) {
+ sealer.destroy();
+ }
+
}
@BeforeMethod
@@ -118,14 +151,17 @@ public class CookieStateManagerTest extends BaseApplicationActionTest {
}
@Test
- public void testMissing() throws IOException {
+ public void testMissing() throws IOException, ComponentInitializationException {
+ setUp(false);
+
request.setCookies(new Cookie(getCookieName(), "foo"));
Assert.assertNull(stateManager.recoverFromStateToken(agent, application, "foo", StateData.class));
}
@Test
- public void testPurge() throws IOException, InterruptedException {
+ public void testPurge() throws IOException, InterruptedException, ComponentInitializationException {
+ setUp(false);
final List<Cookie> cookies = new ArrayList<>(12);
for (int i = 0; i < 12; ++i) {
@@ -145,7 +181,33 @@ public class CookieStateManagerTest extends BaseApplicationActionTest {
}
@Test
- public void testSuccess() throws IOException {
+ public void testSuccessSealed() throws IOException, ComponentInitializationException {
+ setUp(true);
+
+ final StateData source = buildStateData();
+ final String token = stateManager.preserveToStateToken(agent, application, source);
+ assert token != null;
+
+ // Move token set on response to request.
+ request = new MockHttpServletRequest();
+ request.setCookies(response.getCookies());
+ response = new MockHttpServletResponse();
+
+ final StateData recovered = stateManager.recoverFromStateToken(agent, application, token, StateData.class);
+ Assert.assertEquals(source, recovered);
+
+ // Check that old token is unset.
+ final Cookie[] cookies = response.getCookies();
+ Assert.assertEquals(cookies.length, 1);
+ Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "default" + '_' + token);
+ Assert.assertEquals(cookies[0].getValue(), null);
+ Assert.assertEquals(cookies[0].getMaxAge(), 0);
+ Assert.assertEquals(cookies[0].getAttribute("SameSite"), SameSiteValue.None.getValue());
+ }
+
+ @Test
+ public void testSuccess() throws IOException, ComponentInitializationException {
+ setUp(false);
final StateData source = buildStateData();
final String token = stateManager.preserveToStateToken(agent, application, source);
@@ -162,14 +224,15 @@ public class CookieStateManagerTest extends BaseApplicationActionTest {
// Check that old token is unset.
final Cookie[] cookies = response.getCookies();
Assert.assertEquals(cookies.length, 1);
- Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "test" + '_' + token);
+ Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "default" + '_' + token);
Assert.assertEquals(cookies[0].getValue(), null);
Assert.assertEquals(cookies[0].getMaxAge(), 0);
Assert.assertEquals(cookies[0].getAttribute("SameSite"), SameSiteValue.None.getValue());
}
@Test
- public void testSuccessWithAddress() throws IOException {
+ public void testSuccessWithAddress() throws IOException, ComponentInitializationException {
+ setUp(false);
final StateData source = buildStateData();
source.setClientAddress("192.168.1.1");
@@ -188,14 +251,15 @@ public class CookieStateManagerTest extends BaseApplicationActionTest {
// Check that old token is unset.
final Cookie[] cookies = response.getCookies();
Assert.assertEquals(cookies.length, 1);
- Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "test" + '_' + token);
+ Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "default" + '_' + token);
Assert.assertEquals(cookies[0].getValue(), null);
Assert.assertEquals(cookies[0].getMaxAge(), 0);
Assert.assertEquals(cookies[0].getAttribute("SameSite"), SameSiteValue.None.getValue());
}
@Test
- public void testFailureWithAddress() throws IOException {
+ public void testFailureWithAddress() throws IOException, ComponentInitializationException {
+ setUp(false);
final StateData source = buildStateData();
source.setClientAddress("192.168.1.1");
@@ -214,7 +278,7 @@ public class CookieStateManagerTest extends BaseApplicationActionTest {
// Check that old token is unset.
final Cookie[] cookies = response.getCookies();
Assert.assertEquals(cookies.length, 1);
- Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "test" + '_' + token);
+ Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "default" + '_' + token);
Assert.assertEquals(cookies[0].getValue(), null);
Assert.assertEquals(cookies[0].getMaxAge(), 0);
Assert.assertEquals(cookies[0].getAttribute("SameSite"), SameSiteValue.None.getValue());
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/StorageServiceStateManagerTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/StorageServiceStateManagerTest.java
index 366747e..f83c6d0 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/StorageServiceStateManagerTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/StorageServiceStateManagerTest.java
@@ -22,6 +22,7 @@ import java.time.Instant;
import javax.annotation.Nonnull;
import org.opensaml.storage.impl.MemoryStorageService;
+import org.springframework.core.io.ClassPathResource;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.testng.Assert;
@@ -41,8 +42,12 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.net.CookieManager;
import net.shibboleth.shared.net.CookieManager.SameSiteValue;
import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.shared.resource.Resource;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.impl.BasicKeystoreKeyStrategy;
import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
import net.shibboleth.sp.state.StateData;
+import net.shibboleth.sp.testing.TestResourceConverter;
/**
* Unit tests for {@link StorageServiceStateManager}.
@@ -60,6 +65,10 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
+ private Resource keystoreResource;
+ private Resource versionResource;
+
+ private DataSealer sealer;
private CookieManager cookieManager;
private MemoryStorageService storageService;
@@ -87,11 +96,35 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
storageService.setId("test");
storageService.setCleanupInterval(Duration.ZERO);
storageService.initialize();
+
+ ClassPathResource resource =
+ new ClassPathResource("/net/shibboleth/sp/profile/impl/SealerKeyStore.jks");
+ Assert.assertTrue(resource.exists());
+ keystoreResource = TestResourceConverter.of(resource);
+
+ resource =
+ new ClassPathResource("/net/shibboleth/sp/profile/impl/SealerKeyStore.kver");
+ Assert.assertTrue(resource.exists());
+ versionResource = TestResourceConverter.of(resource);
+
+ final BasicKeystoreKeyStrategy strategy = new BasicKeystoreKeyStrategy();
+ strategy.setKeyAlias("secret");
+ strategy.setKeyPassword("kpassword");
+ strategy.setKeystorePassword("password");
+ strategy.setKeystoreResource(keystoreResource);
+ strategy.setKeyVersionResource(versionResource);
+ strategy.initialize();
+
+ sealer = new DataSealer();
+ sealer.setKeyStrategy(strategy);
+ sealer.initialize();
}
@AfterClass
public void tearDown() {
+ cookieManager.destroy();
storageService.destroy();
+ sealer.destroy();
}
@BeforeMethod
@@ -104,13 +137,13 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
@Test
public void testMissingNoCookie() throws IOException, ComponentInitializationException {
- final var stateManager = getStateManager(false);
+ final var stateManager = getStateManager(false, false);
Assert.assertNull(stateManager.recoverFromStateToken(agent, application, "foo", StateData.class));
}
@Test
public void testSuccessNoCookie() throws IOException, ComponentInitializationException {
- final var stateManager = getStateManager(false);
+ final var stateManager = getStateManager(false, false);
final StateData source = buildStateData();
@@ -123,9 +156,24 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
Assert.assertNull(stateManager.recoverFromStateToken(agent, application, token, StateData.class));
}
+ @Test
+ public void testSuccessNoCookieSealed() throws IOException, ComponentInitializationException {
+ final var stateManager = getStateManager(false, true);
+
+ final StateData source = buildStateData();
+
+ final String token = stateManager.preserveToStateToken(agent, application, source);
+ assert token != null;
+
+ final StateData recovered = stateManager.recoverFromStateToken(agent, application, token, StateData.class);
+ Assert.assertEquals(source, recovered);
+
+ Assert.assertNull(stateManager.recoverFromStateToken(agent, application, token, StateData.class));
+ }
+
@Test
public void testSuccessNoCookieWithAddress() throws IOException, ComponentInitializationException {
- final var stateManager = getStateManager(false);
+ final var stateManager = getStateManager(false, false);
final StateData source = buildStateData();
source.setClientAddress("192.168.1.1");
@@ -144,7 +192,7 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
@Test
public void testFailureNoCookieWithAddress() throws IOException, ComponentInitializationException {
- final var stateManager = getStateManager(false);
+ final var stateManager = getStateManager(false, false);
final StateData source = buildStateData();
source.setClientAddress("192.168.1.1");
@@ -163,7 +211,7 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
@Test
public void testSuccessWithCookie() throws IOException, ComponentInitializationException {
- final var stateManager = getStateManager(true);
+ final var stateManager = getStateManager(true, false);
final StateData source = buildStateData();
@@ -181,7 +229,33 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
// Check that old token is unset.
final Cookie[] cookies = response.getCookies();
Assert.assertEquals(cookies.length, 1);
- Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "test" + '_' + token);
+ Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "default" + '_' + token);
+ Assert.assertEquals(cookies[0].getValue(), null);
+ Assert.assertEquals(cookies[0].getMaxAge(), 0);
+ Assert.assertEquals(cookies[0].getAttribute("SameSite"), SameSiteValue.None.getValue());
+ }
+
+ @Test
+ public void testSuccessWithCookieSealed() throws IOException, ComponentInitializationException {
+ final var stateManager = getStateManager(true, true);
+
+ final StateData source = buildStateData();
+
+ final String token = stateManager.preserveToStateToken(agent, application, source);
+ assert token != null;
+
+ // Move cookie set on response to request.
+ request = new MockHttpServletRequest();
+ request.setCookies(response.getCookies());
+ response = new MockHttpServletResponse();
+
+ final StateData recovered = stateManager.recoverFromStateToken(agent, application, token, StateData.class);
+ Assert.assertEquals(source, recovered);
+
+ // Check that old token is unset.
+ final Cookie[] cookies = response.getCookies();
+ Assert.assertEquals(cookies.length, 1);
+ Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "default" + '_' + token);
Assert.assertEquals(cookies[0].getValue(), null);
Assert.assertEquals(cookies[0].getMaxAge(), 0);
Assert.assertEquals(cookies[0].getAttribute("SameSite"), SameSiteValue.None.getValue());
@@ -189,7 +263,7 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
@Test
public void testSuccessWithCookieWithAddress() throws IOException, ComponentInitializationException {
- final var stateManager = getStateManager(true);
+ final var stateManager = getStateManager(true, false);
final StateData source = buildStateData();
source.setClientAddress("192.168.1.1");
@@ -209,7 +283,7 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
// Check that old token is unset.
final Cookie[] cookies = response.getCookies();
Assert.assertEquals(cookies.length, 1);
- Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "test" + '_' + token);
+ Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "default" + '_' + token);
Assert.assertEquals(cookies[0].getValue(), null);
Assert.assertEquals(cookies[0].getMaxAge(), 0);
Assert.assertEquals(cookies[0].getAttribute("SameSite"), SameSiteValue.None.getValue());
@@ -217,7 +291,7 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
@Test
public void testFailureWithCookieWithAddress() throws IOException, ComponentInitializationException {
- final var stateManager = getStateManager(true);
+ final var stateManager = getStateManager(true, false);
final StateData source = buildStateData();
source.setClientAddress("192.168.1.1");
@@ -237,13 +311,13 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
// Check that old token is unset.
final Cookie[] cookies = response.getCookies();
Assert.assertEquals(cookies.length, 1);
- Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "test" + '_' + token);
+ Assert.assertEquals(cookies[0].getName(), CookieStateManager.DEFAULT_PREFIX + '_' + "default" + '_' + token);
Assert.assertEquals(cookies[0].getValue(), null);
Assert.assertEquals(cookies[0].getMaxAge(), 0);
Assert.assertEquals(cookies[0].getAttribute("SameSite"), SameSiteValue.None.getValue());
}
- @Nonnull private StorageServiceStateManager getStateManager(final boolean useCookie)
+ @Nonnull private StorageServiceStateManager getStateManager(final boolean useCookie, final boolean sealed)
throws ComponentInitializationException {
final var stateManager = new StorageServiceStateManager();
@@ -254,6 +328,10 @@ public class StorageServiceStateManagerTest extends BaseApplicationActionTest {
stateManager.setCookieManager(cookieManager);
}
+ if (sealed) {
+ stateManager.setDataSealer(sealer);
+ }
+
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
stateManager.setObjectMapper(mapper);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list