[java-plugin-shibd] branch main updated: Redesign of state management and porting over cookie-backed version.
Codeberg
noreply at shibboleth.net
Mon Apr 13 20:48:06 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/7987472e8e3b6f4c8818e230d88c90e9efaf8ca0
The following commit(s) were added to refs/heads/main by this push:
new 7987472 Redesign of state management and porting over cookie-backed version.
7987472 is described below
commit 7987472e8e3b6f4c8818e230d88c90e9efaf8ca0
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Mon Apr 13 16:47:30 2026 -0400
Redesign of state management and porting over cookie-backed version.
---
pom.xml | 8 +
sp-server-api/pom.xml | 21 ++
.../shibboleth/sp/context/StateDataContext.java | 52 ++++
.../sp/profile/AbstractStateTokenManager.java | 3 +
.../shibboleth/sp/profile/StateTokenManager.java | 3 +
.../shibboleth/sp/state/AbstractStateManager.java | 293 +++++++++++++++++++++
.../java/net/shibboleth/sp/state/StateData.java | 263 ++++++++++++++++++
.../StateManager.java} | 26 +-
.../java/net/shibboleth/sp/state/package-info.java | 18 ++
.../sp/impl/CookieStateTokenManager.java | 3 +
.../sp/impl/PassthroughStateTokenManager.java | 3 +
.../sp/impl/StorageServiceStateTokenManager.java | 3 +
.../impl/CookieStateManager.java} | 65 ++---
.../net/shibboleth/sp/state/impl/package-info.java | 18 ++
.../sp/impl/CookieStateTokenManagerTest.java | 4 +-
.../sp/impl/PassthroughStateTokenManagerTest.java | 4 +-
.../impl/StorageServiceStateTokenManagerTest.java | 4 +-
...ionTest.java => BaseApplicationActionTest.java} | 2 +-
.../profile/impl/IssueCorrelationCookieTest.java | 2 +-
.../sp/profile/impl/IssueDiscoveryRequestTest.java | 2 +-
.../profile/impl/MapResourceToStateTokenTest.java | 2 +-
.../sp/profile/impl/PreservePostDataTest.java | 2 +-
.../profile/impl/ProcessCorrelationCookieTest.java | 2 +-
.../sp/profile/impl/RecoverPostDataTest.java | 2 +-
.../impl/ValidateSessionInitiatorRequestTest.java | 2 +-
.../impl/CookieStateManagerTest.java} | 45 +++-
26 files changed, 782 insertions(+), 70 deletions(-)
diff --git a/pom.xml b/pom.xml
index 3538031..a196d4f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -123,6 +123,14 @@
<type>pom</type>
<scope>import</scope>
</dependency>
+ <!-- Jackson BOM -->
+ <dependency>
+ <groupId>com.fasterxml.jackson</groupId>
+ <artifactId>jackson-bom</artifactId>
+ <version>${jackson.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
</dependencies>
</dependencyManagement>
diff --git a/sp-server-api/pom.xml b/sp-server-api/pom.xml
index 7ece971..ab81bce 100644
--- a/sp-server-api/pom.xml
+++ b/sp-server-api/pom.xml
@@ -52,6 +52,27 @@
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jdk8</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
<dependency>
<groupId>${spring.groupId}</groupId>
<artifactId>spring-beans</artifactId>
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/context/StateDataContext.java b/sp-server-api/src/main/java/net/shibboleth/sp/context/StateDataContext.java
new file mode 100644
index 0000000..a6ae775
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/context/StateDataContext.java
@@ -0,0 +1,52 @@
+/*
+ * 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.sp.state.StateData;
+
+/**
+ * A context to hold state information about a request.
+ */
+public class StateDataContext extends BaseContext {
+
+ /** The authentication state data. */
+ @Nullable private StateData stateData;
+
+ /**
+ * Sets the state tracked for a request.
+ *
+ * @param state state data to set
+ *
+ * @return this context
+ */
+ @Nonnull public StateDataContext setAuthnState(@Nullable final StateData state) {
+ stateData = state;
+ return this;
+ }
+
+ /**
+ * Get the state tracked for a request.
+ *
+ * @return the state data object
+ */
+ @Nullable public StateData getAuthnState() {
+ return stateData;
+ }
+
+}
\ 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
index 9ac8b90..5580661 100644
--- 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
@@ -27,7 +27,10 @@ import net.shibboleth.shared.security.IdentifierGenerationStrategy.ProviderType;
/**
* Base class for {@link StateTokenManager} implementations.
+ *
+ * @deprecated
*/
+ at Deprecated
public abstract class AbstractStateTokenManager extends AbstractIdentifiableInitializableComponent
implements StateTokenManager {
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
index 83ae7d3..f542405 100644
--- 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
@@ -33,7 +33,10 @@ import net.shibboleth.sp.Application;
* <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 {
/**
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
new file mode 100644
index 0000000..9dae5d8
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/state/AbstractStateManager.java
@@ -0,0 +1,293 @@
+/*
+ * 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.state;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.storage.ReplayCache;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.DataSealerException;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy.ProviderType;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.Application;
+
+/**
+ * Base class for {@link StateManager} implementations.
+ */
+public abstract class AbstractStateManager extends AbstractIdentifiableInitializableComponent
+ implements StateManager {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractStateManager.class);
+
+ /** Identifier generation. */
+ @NonnullAfterInit private IdentifierGenerationStrategy identifierStrategy;
+
+ /** JSON object mapper. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+ /** Optional component to protect the data from tampering/visibility. */
+ @Nullable private DataSealer dataSealer;
+
+ /** Optional component to prevent replay of state. */
+ @Nullable private ReplayCache replayCache;
+
+ /** Expiration for state tokens. */
+ @Nonnull private Duration expiration;
+
+ /**
+ * Constructor.
+ */
+ @SuppressWarnings("null")
+ public AbstractStateManager() {
+ 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");
+ }
+
+ /**
+ * Set the JSON {@link ObjectMapper} to use for serialization.
+ *
+ * @param mapper object mapper
+ */
+ public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+ checkSetterPreconditions();
+
+ objectMapper = Constraint.isNotNull(mapper, "ObjectMapper cannot be null");
+ }
+
+ /**
+ * Gets the {@link DataSealer} to use to protect data from tampering/visbility.
+ *
+ * @return data sealer or null
+ */
+ @Nullable public DataSealer getDataSealer() {
+ return dataSealer;
+ }
+
+ /**
+ * Sets the {@link DataSealer} to use to protect data from tampering/visbility.
+ *
+ * @param sealer data sealer
+ */
+ public void setDataSealer(@Nullable final DataSealer sealer) {
+ checkSetterPreconditions();
+
+ dataSealer = sealer;
+ }
+
+ /**
+ * Gets the {@link ReplayCache} to use to prevent replay of state.
+ *
+ * @return replay cache or null
+ */
+ @Nullable public ReplayCache getReplayCache() {
+ return replayCache;
+ }
+
+ /**
+ * Sets the {@link ReplayCache} to use to prevent replay of state.
+ *
+ * <p>This is an additional layer of protection over and above the clearing of
+ * state that takes place routinely.</p>
+ *
+ * @param cache replay cache
+ */
+ public void setReplayCache(@Nullable final ReplayCache cache) {
+ checkSetterPreconditions();
+
+ replayCache = cache;
+ }
+
+ /**
+ * 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 (objectMapper == null) {
+ throw new ComponentInitializationException("ObjectMapper cannot be null");
+ }
+
+ if (identifierStrategy == null) {
+ identifierStrategy = IdentifierGenerationStrategy.getInstance(ProviderType.SECURE);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull public String preserveToStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
+ @Nonnull final StateData data)
+ throws IOException {
+
+ try {
+ // Serialize the input data.
+ final String serializedState = objectMapper.writeValueAsString(data);
+ if (serializedState == null) {
+ throw new IOException("Data could not be serialized into JSON.");
+ }
+ final DataSealer localDataSealer = dataSealer;
+ if (localDataSealer != null) {
+ log.debug("State data will be sealed before preservation");
+ return doPreserve(agent, application, localDataSealer.wrap(serializedState,
+ Instant.now().plus(expiration)));
+ } else {
+ return doPreserve(agent, application, serializedState);
+ }
+ } catch (final JsonProcessingException | DataSealerException e) {
+ throw new IOException("Error preserving state", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public <T extends StateData> T recoverFromStateToken(@Nonnull final Agent agent,
+ @Nonnull final Application application, @Nonnull @NotEmpty final String token,
+ @Nonnull final Class<T> type) throws IOException {
+
+ final ReplayCache localCache = replayCache;
+ if (localCache != null) {
+ log.debug("Checking state token '{}' for replay", token);
+ if (!localCache.check(getClass().getName() + '!' + agent.getId() + '!' + application.getApplicationId(),
+ token, Instant.now().plus(expiration))) {
+ log.warn("Replay of state token '{}' detected", token);
+ return null;
+ }
+ }
+
+ String data = doRecover(agent, application, token);
+ if (data == null) {
+ return null;
+ }
+
+ try {
+ final DataSealer localDataSealer = dataSealer;
+ if (localDataSealer != null) {
+ log.debug("Unwrapping sealed state data after recovery from token '{}'", token);
+ data = localDataSealer.unwrap(data);
+ }
+
+ final T stateData = objectMapper.readValue(data, type);
+ final Instant issued = stateData.getRequestTime();
+ if (issued != null && issued.plus(expiration).isBefore(Instant.now())) {
+ log.warn("State data for token '{}' has expired", token);
+ return null;
+ }
+ return stateData;
+
+ } catch (final DataSealerException|JsonProcessingException e) {
+ throw new IOException("Error unwrapping or parsing state", e);
+ }
+ }
+
+ /**
+ * Subclasses implement this method to preserve the transformed data in whatever way is necessary and return
+ * a token.
+ *
+ * @param agent agent owning the state
+ * @param application application owning the state
+ * @param data data to preserve
+ *
+ * @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;
+
+ /**
+ * Subclasses implement this method to recover the stored data in whatever way is necessary and return
+ * the supplied data string as a successful result.
+ *
+ * <p>Subclasses may assume that the state token inputs they receive will have been returned by them
+ * via the {@link #doPreserve(Agent, Application, String)} method.</p>
+ *
+ * @param agent agent owning the state
+ * @param application application owning the state
+ * @param stateToken the state token to map from/clear
+ *
+ * @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;
+
+ /**
+ * 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/state/StateData.java b/sp-server-api/src/main/java/net/shibboleth/sp/state/StateData.java
new file mode 100644
index 0000000..aa7b6d3
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/state/StateData.java
@@ -0,0 +1,263 @@
+/*
+ * 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.state;
+
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * A DTO class that carries protocol state information that needs to be recovered to validate a protocol
+ * response. This class is designed for JSON serialization and deserialization for storage e.g. in a cookie
+ * or database.
+ *
+ * <p>This class is designed to be extended to support non-generic protocol state as required.</p>
+ *
+ * <p>Any URLs managed by this class are typed as String and strictly opaque to this layer so as to
+ * support any character encoding necessary without assuming UTF-8. Callers should take care to encode
+ * any URLs as required (e.g., even base64 encoding is acceptable).</p>
+ */
+ at JsonInclude(JsonInclude.Include.NON_EMPTY)
+ at NotThreadSafe
+public class StateData {
+
+ /** The identifier of the party that is making a request, i.e., us. */
+ @Nullable private String issuer;
+
+ /** The expected issuer of the authentication response. */
+ @Nullable private String authenticatingAuthority;
+
+ /** The time at which a request was issued. */
+ @Nullable private Instant requestTime;
+
+ /** List of authentication context class references requested in the authentication request. */
+ @Nonnull @Unmodifiable @NotLive private List<String> acrs;
+
+ /** The location to which a response is expected. */
+ @Nullable private String responseLocation;
+
+ /** A resource location associated with the request. */
+ @Nullable private String resource;
+
+ /** Constructor.*/
+ public StateData() {
+ acrs = CollectionSupport.emptyList();
+ }
+
+ /**
+ * Get the identifier of the party that is making the request, i.e., us.
+ *
+ * <p>Can be used to ensure the audience of the response matches the client that made the request.</p>
+ *
+ * @return the issuer
+ */
+ @JsonProperty("issuer")
+ @Nullable public String getIssuer() {
+ return issuer;
+ }
+
+ /**
+ * Set the identifier of the client that is making the authentication request. Can be used to ensure the audience
+ * of the response matches the client that made the request.
+ *
+ * @param id The client ID to set.
+ *
+ * @return the updated object
+ */
+ @Nonnull public StateData setIssuer(@Nullable final String id) {
+ issuer = id;
+ return this;
+ }
+
+ /**
+ * Get the expected issuer of the authentication response.
+ *
+ * @return the authentication authority
+ */
+ @JsonProperty("authority")
+ @Nullable public String getAuthenticationAuthority() {
+ return authenticatingAuthority;
+ }
+
+ /**
+ * Set the expected issuer of the authentication response.
+ *
+ * @param authority the authentication authority to set
+ *
+ * @return the updated object
+ */
+ @Nonnull public StateData setAuthenticationAuthority(@Nullable final String authority) {
+ authenticatingAuthority = authority;
+ return this;
+ }
+
+ /**
+ * Get the time at which the request was issued.
+ *
+ * @return the time the request was issued
+ */
+ @JsonProperty("req_time")
+ @Nullable public Instant getRequestTime() {
+ return requestTime;
+ }
+
+ /**
+ * Set the time at which the request was issued.
+ *
+ * @param time the time the request was issued
+ *
+ * @return the updated object
+ */
+ @Nonnull public StateData setRequestTime(@Nullable final Instant time) {
+ requestTime = time;
+ return this;
+ }
+
+ /**
+ * Get the context classes requested.
+ *
+ * @return the context classes
+ */
+ @JsonProperty("acrs")
+ @Nonnull @Unmodifiable @NotLive public List<String> getAcrs() {
+ return acrs;
+ }
+
+ /**
+ * Set the context classes requested.
+ *
+ * @param refs context class references
+ *
+ * @return the updated object
+ */
+ @Nonnull public StateData setAcrs(@Nullable final List<String> refs) {
+ if (refs != null) {
+ acrs = CollectionSupport.copyToList(refs);
+ } else {
+ acrs = CollectionSupport.emptyList();
+ }
+ return this;
+ }
+ /**
+ * Get the location to which the response is expected to be sent.
+ *
+ * @return the expected response location
+ */
+ @JsonProperty("resp_loc")
+ @Nullable public String getResponseLocation() {
+ return responseLocation;
+ }
+
+ /**
+ * Set the location to which the response is expected to be sent.
+ *
+ * @param loc the location
+ *
+ * @return the updated object
+ */
+ public StateData setResponseLocation(@Nullable final String loc) {
+ responseLocation = loc;
+ return this;
+ }
+
+ /**
+ * Get the resource location associated with the request.
+ *
+ * @return the resource location
+ */
+ @JsonProperty("resource")
+ @Nullable public String getResource() {
+ return resource;
+ }
+
+ /**
+ * Set the resource location associated with the request.
+ *
+ * @param loc the location
+ *
+ * @return the updated object
+ */
+ public StateData setResource(@Nullable final String loc) {
+ resource = loc;
+ return this;
+ }
+
+
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(acrs, authenticatingAuthority, issuer, requestTime, resource, responseLocation);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ final StateData other = (StateData) obj;
+ return Objects.equals(acrs, other.acrs)
+ && Objects.equals(authenticatingAuthority, other.authenticatingAuthority)
+ && Objects.equals(issuer, other.issuer) && Objects.equals(requestTime, other.requestTime)
+ && Objects.equals(resource, other.resource) && Objects.equals(responseLocation, other.responseLocation);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("issuer", issuer)
+ .add("authenticatingAuthority", authenticatingAuthority)
+ .add("requestTime", requestTime)
+ .add("acrs", acrs)
+ .add("reponseLocation", responseLocation)
+ .add("resource", resource)
+ .toString();
+ }
+
+ /**
+ * A method to mask all but the last 2 characters of a string value for logging purposes.
+ *
+ * @param value the string value to mask
+ * @return the masked string
+ */
+ protected static String mask(final String value) {
+ if (value == null) {
+ return null;
+ }
+ if (value.length() <= 4) {
+ return "****";
+ }
+ return "****" + value.substring(value.length() - 2);
+ }
+
+}
\ No newline at end of file
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/state/StateManager.java
similarity index 71%
copy from sp-server-api/src/main/java/net/shibboleth/sp/profile/StateTokenManager.java
copy to sp-server-api/src/main/java/net/shibboleth/sp/state/StateManager.java
index 83ae7d3..a6d2e23 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/profile/StateTokenManager.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/state/StateManager.java
@@ -12,7 +12,7 @@
* limitations under the License.
*/
-package net.shibboleth.sp.profile;
+package net.shibboleth.sp.state;
import java.io.IOException;
@@ -30,41 +30,43 @@ import net.shibboleth.sp.Application;
* <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>
+ * <p>There are multiple possible implementations of this concept, all involving cookies to
+ * provide CSRF protection.</p>
*/
-public interface StateTokenManager {
+public interface StateManager {
/**
- * Preserves a value by transforming it into a state token.
+ * Preserves data while transforming it into a state token.
*
* @param agent agent owning the state
* @param application application owning the state
- * @param value input value to preserve
+ * @param data data 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;
+ @Nonnull final StateData data) throws IOException;
/**
- * Recovers a value from a state token.
+ * Recovers stored data from a state token.
*
* <p>In most implementations, the state token mapping should be cleared on successful use of this
* method.</p>
*
+ * @param <T> type of state
+ *
* @param agent agent owning the state
* @param application application owning the state
* @param token state token
+ * @param type specific subtype of {@link StateData} to recover
*
- * @return the recovered value, or null if unable to recover without underlying cause
+ * @return the recovered data, 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;
+ @Nullable <T extends StateData> T recoverFromStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
+ @Nonnull final String token, @Nonnull Class<T> type) throws IOException;
}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/state/package-info.java b/sp-server-api/src/main/java/net/shibboleth/sp/state/package-info.java
new file mode 100644
index 0000000..8f61e3d
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/state/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * APIs supporting state management for SSO protocols.
+ */
+package net.shibboleth.sp.state;
\ No newline at end of file
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
index e281d61..ab832d3 100644
--- 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
@@ -44,7 +44,10 @@ import net.shibboleth.sp.profile.StateTokenManager;
/**
* {@link StateTokenManager} implemented using cookies.
+ *
+ * @deprecated
*/
+ at Deprecated
public class CookieStateTokenManager extends AbstractStateTokenManager {
/** Default cookie prefix. */
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
index 9649c8a..c00b485 100644
--- 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
@@ -32,7 +32,10 @@ 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 {
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
index aa5f095..d89a6c1 100644
--- 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
@@ -38,7 +38,10 @@ import net.shibboleth.sp.profile.StateTokenManager;
/**
* {@link StateTokenManager} implemented with a {@link StorageService}.
+ *
+ * @deprecated
*/
+ at Deprecated
public class StorageServiceStateTokenManager extends AbstractStateTokenManager {
/** Class logger. */
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/state/impl/CookieStateManager.java
similarity index 80%
copy from sp-server-impl/src/main/java/net/shibboleth/sp/impl/CookieStateTokenManager.java
copy to sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/CookieStateManager.java
index e281d61..5753412 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/CookieStateTokenManager.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/CookieStateManager.java
@@ -12,9 +12,10 @@
* limitations under the License.
*/
-package net.shibboleth.sp.impl;
+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;
@@ -39,19 +40,19 @@ 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;
+import net.shibboleth.sp.state.AbstractStateManager;
+import net.shibboleth.sp.state.StateManager;
/**
- * {@link StateTokenManager} implemented using cookies.
+ * {@link StateManager} implemented by storing the data directly into a cookie.
*/
-public class CookieStateTokenManager extends AbstractStateTokenManager {
+public class CookieStateManager extends AbstractStateManager {
/** Default cookie prefix. */
- @Nonnull @NotEmpty public static String DEFAULT_PREFIX = "_shibsp_state_";
+ @Nonnull @NotEmpty public static String DEFAULT_PREFIX = "_shibsp_state";
/** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(CookieStateTokenManager.class);
+ @Nonnull private Logger log = LoggerFactory.getLogger(CookieStateManager.class);
/** Cookie manager. */
@NonnullAfterInit private CookieManager cookieManager;
@@ -60,7 +61,7 @@ public class CookieStateTokenManager extends AbstractStateTokenManager {
@Nonnull @NotEmpty private String cookiePrefix;
/** Constructor. */
- public CookieStateTokenManager() {
+ public CookieStateManager() {
cookiePrefix = DEFAULT_PREFIX;
}
@@ -91,12 +92,7 @@ public class CookieStateTokenManager extends AbstractStateTokenManager {
/** {@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 {
@@ -105,12 +101,19 @@ public class CookieStateTokenManager extends AbstractStateTokenManager {
throw new ComponentInitializationException(e);
}
}
+
+ super.doInitialize();
+
+ if (cookieManager == null) {
+ throw new ComponentInitializationException("CookieManager cannot be null");
+ }
}
-
+
/** {@inheritDoc} */
- @Nonnull public String preserveToStateToken(@Nonnull final Agent agent, @Nonnull final Application application,
- @Nonnull final byte[] value) throws IOException {
-
+ @Override
+ @Nonnull protected String doPreserve(@Nonnull final Agent agent, @Nonnull final Application application,
+ @Nonnull final String data) throws IOException {
+
cookieManager.purgeStaleCookies(cookiePrefix);
final Instant ts = Instant.now();
@@ -119,41 +122,39 @@ public class CookieStateTokenManager extends AbstractStateTokenManager {
final String key = Long.toString(ts.toEpochMilli()) + '_' + generateToken();
final String name = getCookieName(application, key);
try {
- cookieManager.addCookie(name, Base64Support.encodeURLSafe(value), (int) getExpiration().toSeconds());
+ 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, value);
+ log.trace("Created state token mapping from '{}' to value '{}'", name, data);
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;
- }
+ @Override
+ @Nullable protected String doRecover(@Nonnull final Agent agent, @Nonnull final Application application,
+ @Nonnull @NotEmpty final String stateToken) throws IOException {
- final String cookieName = getCookieName(application, token);
+ final String cookieName = getCookieName(application, stateToken);
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);
+ 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);
}
}
- log.warn("No cookie found matching state token: '{}'", token);
+ log.warn("No cookie found matching state token: '{}'", stateToken);
return null;
- }
+ }
/**
* Computes the name of a new state cookie.
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/package-info.java b/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/package-info.java
new file mode 100644
index 0000000..e3a6e60
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/state/impl/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implementation of protocol state management.
+ */
+package net.shibboleth.sp.state.impl;
\ No newline at end of file
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
index a7015cb..2952d7a 100644
--- 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
@@ -36,13 +36,13 @@ 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.BaseAgplicationActionTest;
+import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
/**
* Unit tests for {@link CookieStateTokenManager}.
*/
@SuppressWarnings("javadoc")
-public class CookieStateTokenManagerTest extends BaseAgplicationActionTest {
+public class CookieStateTokenManagerTest extends BaseApplicationActionTest {
private CookieManager cookieManager;
private CookieStateTokenManager stateManager;
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
index d20e3f3..c78bfcb 100644
--- 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
@@ -23,13 +23,13 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.sp.profile.impl.BaseAgplicationActionTest;
+import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
/**
* Unit tests for {@link PassthroughStateTokenManager}.
*/
@SuppressWarnings("javadoc")
-public class PassthroughStateTokenManagerTest extends BaseAgplicationActionTest {
+public class PassthroughStateTokenManagerTest extends BaseApplicationActionTest {
private PassthroughStateTokenManager stateManager;
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
index dc90a45..e9f1a0c 100644
--- 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
@@ -25,13 +25,13 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.sp.profile.impl.BaseAgplicationActionTest;
+import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
/**
* Unit tests for {@link StorageServiceStateTokenManager}.
*/
@SuppressWarnings("javadoc")
-public class StorageServiceStateTokenManagerTest extends BaseAgplicationActionTest {
+public class StorageServiceStateTokenManagerTest extends BaseApplicationActionTest {
private MemoryStorageService storageService;
private StorageServiceStateTokenManager stateManager;
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgplicationActionTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseApplicationActionTest.java
similarity index 94%
rename from sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgplicationActionTest.java
rename to sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseApplicationActionTest.java
index 7b60882..7be5a60 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgplicationActionTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseApplicationActionTest.java
@@ -20,7 +20,7 @@ import net.shibboleth.sp.impl.BasicApplication;
/**
* Base class for unit tests that rely on set up of an agent request with an application.
*/
-public abstract class BaseAgplicationActionTest extends BaseAgentRequestTest {
+public abstract class BaseApplicationActionTest extends BaseAgentRequestTest {
protected BasicApplication application;
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
index 85a56ee..9a89f89 100644
--- 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
@@ -46,7 +46,7 @@ import net.shibboleth.sp.profile.SPConstants;
* Unit test for {@link IssueCorrelationCookie} action.
*/
@SuppressWarnings("javadoc")
-public class IssueCorrelationCookieTest extends BaseAgplicationActionTest {
+public class IssueCorrelationCookieTest extends BaseApplicationActionTest {
@Nonnull @NotEmpty private final static String TEST_STATE = "foo";
@Nonnull @NotEmpty private final static String TEST_ID = "123456789";
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 b20b388..089ed29 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
@@ -43,7 +43,7 @@ import net.shibboleth.sp.profile.SPConstants;
* Unit test for {@link IssueDiscoveryRequest} action.
*/
@SuppressWarnings("javadoc")
-public class IssueDiscoveryRequestTest extends BaseAgplicationActionTest {
+public class IssueDiscoveryRequestTest extends BaseApplicationActionTest {
/** Test discovery URL. */
@Nonnull @NotEmpty private final static String TEST_DISCOVERY_URL = "https://ds.example.org/DS";
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
index 4300171..a38e959 100644
--- 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
@@ -47,7 +47,7 @@ import net.shibboleth.sp.profile.SPConstants;
* Unit test for {@link MapResourceToStateToken} action.
*/
@SuppressWarnings("javadoc")
-public class MapResourceToStateTokenTest extends BaseAgplicationActionTest {
+public class MapResourceToStateTokenTest extends BaseApplicationActionTest {
/** Test request URL. */
@Nonnull @NotEmpty private final static String TEST_URL = "https://sp.example.org/cgi-bin/test.cgi";
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 ddb4227..2fd2828 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
@@ -47,7 +47,7 @@ import net.shibboleth.sp.profile.SPConstants;
* Unit test for {@link PreservePostData} action.
*/
@SuppressWarnings("javadoc")
-public class PreservePostDataTest extends BaseAgplicationActionTest {
+public class PreservePostDataTest extends BaseApplicationActionTest {
@Nonnull @NotEmpty private final static String TEST_STATE = "foo";
@Nonnull @NotEmpty private final static String TEST_DATA = "foo=bar&zorkmid=a+b";
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
index 3b37c5d..9c24163 100644
--- 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
@@ -45,7 +45,7 @@ import net.shibboleth.sp.ddf.DDF;
* Unit test for {@link ProcessCorrelationCookie} action.
*/
@SuppressWarnings("javadoc")
-public class ProcessCorrelationCookieTest extends BaseAgplicationActionTest {
+public class ProcessCorrelationCookieTest extends BaseApplicationActionTest {
@Nonnull @NotEmpty private final static String TEST_STATE = "foo";
@Nonnull @NotEmpty private final static String TEST_ID = "123456789";
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/RecoverPostDataTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/RecoverPostDataTest.java
index 283c51d..8e9a1c7 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/RecoverPostDataTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/RecoverPostDataTest.java
@@ -59,7 +59,7 @@ import net.shibboleth.sp.profile.SPConstants;
* Unit test for {@link RecoverPostData} action.
*/
@SuppressWarnings("javadoc")
-public class RecoverPostDataTest extends BaseAgplicationActionTest {
+public class RecoverPostDataTest extends BaseApplicationActionTest {
@Nonnull @NotEmpty private final static String TEST_STATE = "foo";
@Nonnull @NotEmpty private final static String TEST_DATA = "foo=bar&zorkmid=a+b&frobnitz=c:d";
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/ValidateSessionInitiatorRequestTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/ValidateSessionInitiatorRequestTest.java
index 5e3073f..d88be25 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/ValidateSessionInitiatorRequestTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/ValidateSessionInitiatorRequestTest.java
@@ -37,7 +37,7 @@ import net.shibboleth.sp.profile.InitiatorConstants;
* Unit test for {@link ValidateSessionInitiatorRequest} action.
*/
@SuppressWarnings("javadoc")
-public class ValidateSessionInitiatorRequestTest extends BaseAgplicationActionTest {
+public class ValidateSessionInitiatorRequestTest extends BaseApplicationActionTest {
/** Test value. */
@Nonnull @NotEmpty private final static String TEST_VALUE = "https://idp.example.org";
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/state/impl/CookieStateManagerTest.java
similarity index 75%
copy from sp-server-impl/src/test/java/net/shibboleth/sp/impl/CookieStateTokenManagerTest.java
copy to sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/CookieStateManagerTest.java
index a7015cb..a670fdc 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/impl/CookieStateTokenManagerTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/CookieStateManagerTest.java
@@ -12,7 +12,7 @@
* limitations under the License.
*/
-package net.shibboleth.sp.impl;
+package net.shibboleth.sp.state.impl;
import java.io.IOException;
import java.time.Instant;
@@ -29,23 +29,32 @@ import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+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.profile.impl.BaseAgplicationActionTest;
+import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
+import net.shibboleth.sp.state.StateData;
/**
- * Unit tests for {@link CookieStateTokenManager}.
+ * Unit tests for {@link CookieStateManager}.
*/
@SuppressWarnings("javadoc")
-public class CookieStateTokenManagerTest extends BaseAgplicationActionTest {
+public class CookieStateManagerTest extends BaseApplicationActionTest {
+
+ @Nonnull @NotEmpty private static final String TEST_ISSUER = "https://sp.example.org";
+ @Nonnull @NotEmpty private static final String TEST_AUTHORITY = "https://idp.example.org";
+
private CookieManager cookieManager;
- private CookieStateTokenManager stateManager;
+ private CookieStateManager stateManager;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@@ -71,8 +80,11 @@ public class CookieStateTokenManagerTest extends BaseAgplicationActionTest {
});
cookieManager.initialize();
- stateManager = new CookieStateTokenManager();
+ stateManager = new CookieStateManager();
stateManager.setId("test");
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ stateManager.setObjectMapper(mapper);
stateManager.setCookieManager(cookieManager);
stateManager.initialize();
@@ -96,7 +108,7 @@ public class CookieStateTokenManagerTest extends BaseAgplicationActionTest {
public void testMissing() throws IOException {
request.setCookies(new Cookie(getCookieName(), "foo"));
- Assert.assertNull(stateManager.recoverFromStateToken(agent, application, "foo"));
+ Assert.assertNull(stateManager.recoverFromStateToken(agent, application, "foo", StateData.class));
}
@Test
@@ -109,7 +121,7 @@ public class CookieStateTokenManagerTest extends BaseAgplicationActionTest {
}
request.setCookies(cookies.toArray(new Cookie[12]));
- final String token = stateManager.preserveToStateToken(agent, application, "foo".getBytes());
+ final String token = stateManager.preserveToStateToken(agent, application, buildStateData());
assert token != null;
final Cookie[] respCookies = response.getCookies();
@@ -122,7 +134,8 @@ public class CookieStateTokenManagerTest extends BaseAgplicationActionTest {
@Test
public void testMapRecover() throws IOException {
- final String token = stateManager.preserveToStateToken(agent, application, "foo".getBytes());
+ final StateData source = buildStateData();
+ final String token = stateManager.preserveToStateToken(agent, application, source);
assert token != null;
// Move token set on response to request.
@@ -130,13 +143,13 @@ public class CookieStateTokenManagerTest extends BaseAgplicationActionTest {
request.setCookies(response.getCookies());
response = new MockHttpServletResponse();
- final byte[] original = stateManager.recoverFromStateToken(agent, application, token);
- Assert.assertEquals(original, "foo".getBytes());
+ 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(), CookieStateTokenManager.DEFAULT_PREFIX + '_' + "test" + '_' + token);
+ Assert.assertEquals(cookies[0].getName(), CookieStateManager.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());
@@ -147,5 +160,13 @@ public class CookieStateTokenManagerTest extends BaseAgplicationActionTest {
final String rand = stateManager.getIdentifierGenerationStrategy().generateIdentifier(false);
return stateManager.getCookieName(application, now.toEpochMilli() + '_' + rand);
}
+
+ @Nonnull private StateData buildStateData() {
+ final StateData data = new StateData();
+ data.setRequestTime(Instant.now());
+ data.setIssuer(TEST_ISSUER);
+ data.setAuthenticationAuthority(TEST_AUTHORITY);
+ return data;
+ }
}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list