[java-plugin-shibd] branch main updated: Add prefixing capability to state manager.

Codeberg noreply at shibboleth.net
Mon Jun 1 18:42:10 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/37cc5fb7fab4e29ab1d4810c4cc5dc84be3038a5

The following commit(s) were added to refs/heads/main by this push:
     new 37cc5fb  Add prefixing capability to state manager.
37cc5fb is described below

commit 37cc5fb7fab4e29ab1d4810c4cc5dc84be3038a5
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Mon Jun 1 14:41:34 2026 -0400

    Add prefixing capability to state manager.
---
 .../shibboleth/sp/state/AbstractStateManager.java  | 28 +++++++++--
 .../sp/state/impl/PassthroughStateManagerTest.java | 55 ++++++++++++++++++++--
 2 files changed, 76 insertions(+), 7 deletions(-)

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 26ccc79..8d9c102 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
@@ -35,6 +35,7 @@ import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.shared.primitive.StringSupport;
 import net.shibboleth.shared.security.DataSealer;
 import net.shibboleth.shared.security.DataSealerException;
 import net.shibboleth.shared.security.IdentifierGenerationStrategy;
@@ -56,7 +57,10 @@ public abstract class AbstractStateManager extends AbstractIdentifiableInitializ
     
     /** Identifier generation. */
     @NonnullAfterInit private IdentifierGenerationStrategy identifierStrategy;
-
+    
+    /** Optional prefix to attach to token. */
+    @Nonnull private String prefix;
+    
     /** JSON object mapper. */
     @NonnullAfterInit private ObjectMapper objectMapper;
     
@@ -74,6 +78,7 @@ public abstract class AbstractStateManager extends AbstractIdentifiableInitializ
      */
     @SuppressWarnings("null")
     public AbstractStateManager() {
+        prefix = "";
         expiration = Duration.ofMinutes(30);
     }
     
@@ -108,6 +113,16 @@ public abstract class AbstractStateManager extends AbstractIdentifiableInitializ
         
         identifierStrategy = Constraint.isNotNull(strategy, "IdentifierGenerationStrategy cannot be null");
     }
+        
+    /**
+     * Sets optional prefix to apply to the token.
+     * 
+     * @param s prefix string
+     */
+    public void setPrefix(@Nullable final String s) {
+        final String trimmed = StringSupport.trimOrNull(s);
+        prefix = trimmed != null ? trimmed : "";
+    }
     
     /**
      * Set the JSON {@link ObjectMapper} to use for serialization.
@@ -213,10 +228,10 @@ public abstract class AbstractStateManager extends AbstractIdentifiableInitializ
             final DataSealer localDataSealer = dataSealer;
             if (localDataSealer != null) {
                 log.debug("State data will be sealed before preservation");
-                return doPreserve(agent, application, localDataSealer.wrap(serializedState,
+                return prefix + doPreserve(agent, application, localDataSealer.wrap(serializedState,
                         Instant.now().plus(expiration)), true);
             } else {
-                return doPreserve(agent, application, serializedState, false);
+                return prefix + doPreserve(agent, application, serializedState, false);
             }
         } catch (final JsonProcessingException | DataSealerException e) {
             throw new IOException("Error preserving state", e);
@@ -227,6 +242,11 @@ public abstract class AbstractStateManager extends AbstractIdentifiableInitializ
     @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 {
+
+        if (prefix.length() > 0 && !token.startsWith(prefix)) {
+            log.warn("State token '{}' did not start with expected prefix ({})", token, prefix);
+            return null;
+        }
         
         final ReplayCache localCache = replayCache;
         if (localCache != null) {
@@ -238,7 +258,7 @@ public abstract class AbstractStateManager extends AbstractIdentifiableInitializ
             }
         }
         
-        String data = doRecover(agent, application, token, dataSealer != null);
+        String data = doRecover(agent, application, token.substring(prefix.length()), dataSealer != null);
         if (data == null) {
             return null;
         }
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/PassthroughStateManagerTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/PassthroughStateManagerTest.java
index 3d7b792..4aeea7b 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/PassthroughStateManagerTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/state/impl/PassthroughStateManagerTest.java
@@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.springframework.core.io.ClassPathResource;
 import org.testng.Assert;
@@ -88,7 +89,7 @@ public class PassthroughStateManagerTest extends BaseApplicationActionTest {
         
     @Test
     public void testSuccess() throws IOException, ComponentInitializationException {
-        final var stateManager = getStateManager(false);
+        final var stateManager = getStateManager(false, null);
         
         final StateData source = buildStateData();
         
@@ -101,7 +102,7 @@ public class PassthroughStateManagerTest extends BaseApplicationActionTest {
 
     @Test
     public void testSuccessSealed() throws IOException, ComponentInitializationException {
-        final var stateManager = getStateManager(true);
+        final var stateManager = getStateManager(true, null);
         
         final StateData source = buildStateData();
         
@@ -111,12 +112,60 @@ public class PassthroughStateManagerTest extends BaseApplicationActionTest {
         final StateData recovered = stateManager.recoverFromStateToken(agent, application, token, StateData.class);
         Assert.assertEquals(source, recovered);
     }
+
+    @Test
+    public void testPrefixSuccess() throws IOException, ComponentInitializationException {
+        final var stateManager = getStateManager(false, "FOO:");
+        
+        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);
+    }
+
+    @Test
+    public void testPrefixSuccessSealed() throws IOException, ComponentInitializationException {
+        final var stateManager = getStateManager(true, "FOO:");
+        
+        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);
+    }
+    
+    @Test(expectedExceptions=IOException.class)
+    public void testBadSealed() throws IOException, ComponentInitializationException {
+        final var stateManager = getStateManager(true, null);
+        
+        stateManager.recoverFromStateToken(agent, application, "bad", StateData.class);
+    }
+    
+    @Test
+    public void testBadPrefix() throws IOException, ComponentInitializationException {
+        final var stateManager = getStateManager(false, "FOO:");
+        
+        final StateData source = buildStateData();
+        
+        final String token = stateManager.preserveToStateToken(agent, application, source);
+        assert token != null;
+        
+        final StateData recovered = stateManager.recoverFromStateToken(agent, application, token.substring(1), StateData.class);
+        Assert.assertNull(recovered);
+    }
+    
             
-    @Nonnull private PassthroughStateManager getStateManager(final boolean sealed)
+    @Nonnull private PassthroughStateManager getStateManager(final boolean sealed, @Nullable final String prefix)
             throws ComponentInitializationException {
         
         final var stateManager = new PassthroughStateManager();
         stateManager.setId("test");
+        stateManager.setPrefix(prefix);
         
         if (sealed) {
             stateManager.setDataSealer(sealer);

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


More information about the commits mailing list