[java-plugin-shibd] branch main updated: Cached authentication action and unit test.

Scott Cantor cantor.2 at osu.edu
Wed May 22 19:22:41 UTC 2024


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

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

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

The following commit(s) were added to refs/heads/main by this push:
     new ebc1e10  Cached authentication action and unit test.
ebc1e10 is described below

commit ebc1e1038354c26b90cd615624d9aaa9f09252ae
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed May 22 15:22:38 2024 -0400

    Cached authentication action and unit test.
---
 pom.xml                                            |   2 +-
 .../idp/flows/sp/abstract/sp-abstract-beans.xml    |  14 ++
 .../idp/flows/sp/abstract/sp-abstract-flow.xml     |   5 +-
 sp-server-impl/pom.xml                             |  17 +-
 .../authn/impl/ValidateCachedAuthentication.java   |  66 ++++--
 .../sp/authn/impl/TestResourceConverter.java       | 127 +++++++++++
 .../impl/ValidateCachedAuthenticationTest.java     | 249 +++++++++++++++++++++
 .../shibboleth/sp/authn/impl/SealerKeyStore.jks    | Bin 0 -> 984 bytes
 .../shibboleth/sp/authn/impl/SealerKeyStore.kver   |   1 +
 9 files changed, 454 insertions(+), 27 deletions(-)

diff --git a/pom.xml b/pom.xml
index 102ade4..9a3c976 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,7 +67,7 @@
             <artifactId>shib-support</artifactId>
             <scope>provided</scope>
         </dependency>
-        
+
         <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-beans.xml
index f548136..2ebd014 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-beans.xml
@@ -39,4 +39,18 @@
         class="net.shibboleth.sp.authn.impl.ValidateAgentAddress" scope="prototype"
         p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
 
+    <bean id="ValidateCachedAuthentication"
+        class="net.shibboleth.sp.authn.impl.ValidateCachedAuthentication" scope="prototype"
+        p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
+        p:cookieName="%{sp.agent.cachedAuthentication.cookie:__Host-shibsp_agent_token}"
+        p:cookieManager-ref="sp.CookieManager"
+        p:dataSealer-ref="shibboleth.DataSealer" />
+
+    <bean id="sp.CookieManager" parent="shibboleth.PersistentCookieManager"
+        p:secure="true"
+        p:httpOnly="true"
+        p:cookieDomain=""
+        p:cookiePath="/"
+        p:maxAge="%{sp.agent.cachedAuthentication.maxAge:3600}" />
+
 </beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-flow.xml
index 47c2b43..298c66f 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/abstract/sp-abstract-flow.xml
@@ -20,8 +20,9 @@
         <evaluate expression="ValidateCachedAuthentication" />
         <evaluate expression="'proceed'" />
         
-        <transition on="proceed" to="DecodeMessage" />
-        <transition on="NoCredentials" to="DoAuthentication" />
+        <!-- Inverting the usual approach, proceed means "not cached". -->
+        <transition on="proceed" to="DoAuthentication" />
+        <transition on="BypassAuthentication" to="DecodeMessage" />
     </action-state>
 
     <action-state id="DoAuthentication">
diff --git a/sp-server-impl/pom.xml b/sp-server-impl/pom.xml
index 5caa179..d9f0b83 100644
--- a/sp-server-impl/pom.xml
+++ b/sp-server-impl/pom.xml
@@ -87,6 +87,16 @@
             <scope>provided</scope>
         </dependency>
 
+        <dependency>
+            <groupId>${shib-shared.groupId}</groupId>
+            <artifactId>shib-networking</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${shib-shared.groupId}</groupId>
+            <artifactId>shib-security</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>${shib-shared.groupId}</groupId>
             <artifactId>shib-service</artifactId>
@@ -109,16 +119,13 @@
             <artifactId>jcl-over-slf4j</artifactId>
             <scope>provided</scope>
         </dependency>
+
         <dependency>
             <groupId>jakarta.servlet</groupId>
             <artifactId>jakarta.servlet-api</artifactId>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>com.beust</groupId>
-            <artifactId>jcommander</artifactId>
-            <scope>provided</scope>
-        </dependency>
+
 
         <!-- Test Dependencies -->
         <dependency>
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateCachedAuthentication.java b/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateCachedAuthentication.java
index 6358694..90ce90e 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateCachedAuthentication.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/authn/impl/ValidateCachedAuthentication.java
@@ -36,6 +36,7 @@ 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.DataExpiredException;
 import net.shibboleth.shared.security.DataSealer;
 import net.shibboleth.shared.security.DataSealerException;
 import net.shibboleth.shared.servlet.HttpServletSupport;
@@ -46,17 +47,24 @@ import net.shibboleth.sp.context.AgentRequestContext;
  * An action that checks for a sealed cookie authenticating request without the need for
  * validating a shared secret or other credentials.
  * 
- *  <p>The cookie is an address-bound bearer token containing the agent authorized to use it,
- *  the destination, the address, an expiration, etc.</p>
+ * <p>For safety's sake, the default event signal indicates that authentication should
+ * <strong>not</strong> be bypassed. An explicit event is used to signal bypass.</p>
+ * 
+ * <p>The cookie is an address-bound bearer token containing the agent authorized to use it.
+ * The data being sealed, it has an internal expiration independently of the cookie's own.</p>
  *  
- *  <p>TODO: adding a key proof via MAC in some way</p>
+ * <p>TODO: adding a key proof via MAC in some way</p>
  * 
  * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link #BYPASS_AUTHENTICATION}
  * @pre <pre>ProfileRequestContext.ensureSubcontext(AgentRequestContext.class).getAgent() != null</pre>
  */
 public class ValidateCachedAuthentication extends AbstractProfileAction {
 
+    /** Bypass event indicating cookie was accepted. */
+    @Nonnull @NotEmpty public static final String BYPASS_AUTHENTICATION = "BypassAuthentication";
+    
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateCachedAuthentication.class);
     
@@ -152,7 +160,6 @@ public class ValidateCachedAuthentication extends AbstractProfileAction {
         
         if (!agent.isSupportsCachedAuthentication()) {
             log.debug("{} Agent '{}' does not support cached authentication, skipping", getLogPrefix(), agent.getId());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             return false;
         }
         
@@ -162,33 +169,54 @@ public class ValidateCachedAuthentication extends AbstractProfileAction {
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        final HttpServletRequest request = getHttpServletRequest();
-        final String addr = request != null ? HttpServletSupport.getRemoteAddr(request) : null;
-        if (addr == null) {
-            log.warn("{} No client address for request from agent '{}', skipping cached authentication check", getLogPrefix(),
-                    agent.getId());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-            return;
-        }
         
         try {
             assert cookieName != null;
             final String wrapped = cookieManager.getCookieValue(cookieName, null);
             if (wrapped == null) {
-                log.debug("{} No cookie in request from agent '{}', skipping cached authentication check", getLogPrefix(),
+                log.debug("{} No cookie from agent '{}', skipping cached authentication check", getLogPrefix(),
                         agent.getId());
-                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
                 return;
             }
             
             final String unwrapped = dataSealer.unwrap(URISupport.doURLDecode(wrapped));
-            
-            // TODO parse and validate...
-            
+            if (isValid(profileRequestContext, unwrapped)) {
+                log.info("{} Accepted cookie from agent '{}', skipping full authentication", getLogPrefix(),
+                        agent.getId());
+                ActionSupport.buildEvent(profileRequestContext, BYPASS_AUTHENTICATION);
+            } else {
+                log.debug("{} Rejected cookie from agent '{}', full authentication will proceed", getLogPrefix(),
+                        agent.getId());
+            }
+        } catch (final DataExpiredException e) {
+            log.debug("{} Cookie from agent '{}' expired, authentication not bypassed", getLogPrefix(), agent.getId());
         } catch (final DataSealerException e) {
-            log.warn("{} Error decrypting cookie from agent '{}', authentication not bypassed", getLogPrefix(), agent.getId(), e);
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+            log.warn("{} Error decrypting cookie from agent '{}', authentication not bypassed", getLogPrefix(),
+                    agent.getId(), e);
+        }
+    }
+    
+    private boolean isValid(@Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final String cookie) {
+        final String[] split = cookie.split("!");
+        if (split == null || split.length != 2) {
+            log.warn("{} Cookie malformed from agent '{}'", getLogPrefix(), agent.getId());
+            return false;
+        }
+        
+        if (!split[0].equals(agent.getId())) {
+            log.warn("{} Cookie from agent '{}' issued to agent '{}'", getLogPrefix(), agent.getId(), split[0]);
+            return false;
+        }
+        
+        final HttpServletRequest request = getHttpServletRequest();
+        final String addr = request != null ? HttpServletSupport.getRemoteAddr(request) : null;
+        if (!split[1].equals(addr)) {
+            log.warn("{} Cookie from agent '{}' and address {} issued to address {}", getLogPrefix(), agent.getId(),
+                    split[1], addr);
+            return false;
         }
+        
+        return true;
     }
     
 }
\ No newline at end of file
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/TestResourceConverter.java b/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/TestResourceConverter.java
new file mode 100644
index 0000000..04e7ad7
--- /dev/null
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/TestResourceConverter.java
@@ -0,0 +1,127 @@
+/*
+ * 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.authn.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+
+import javax.annotation.Nonnull;
+
+import org.springframework.core.io.Resource;
+
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Bridging class between {@link Resource} and {@link net.shibboleth.shared.resource.Resource}.
+ */
+public final class TestResourceConverter implements net.shibboleth.shared.resource.Resource {
+
+    /** The cached Spring {@link Resource}. */
+    private Resource springResource;
+
+    /**
+     * A private for shimming the provided input.
+     * 
+     * @param theResource the spring resource;
+     */
+    private TestResourceConverter(@Nonnull Resource theResource) {
+
+        springResource = Constraint.isNotNull(theResource, "provided Spring Resource should not be null");
+    }
+
+    /**
+     * Return a {@link Resource} that does all the work of the provided {@link Resource}.
+     * 
+     * <p>
+     * If the input implements {@link Resource} then it is cast to the output, other a shim class is
+     * generated.
+     * </p>
+     * 
+     * @param springResource the input
+     * @return a {@link Resource} which reflects what the Spring one does
+     */
+    @Nonnull public static net.shibboleth.shared.resource.Resource of(@Nonnull Resource springResource) {
+        if (springResource instanceof net.shibboleth.shared.resource.Resource) {
+            return (net.shibboleth.shared.resource.Resource) springResource;
+        }
+        return new TestResourceConverter(springResource);
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull public InputStream getInputStream() throws IOException {
+        return springResource.getInputStream();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean exists() {
+        return springResource.exists();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isReadable() {
+        return springResource.isReadable();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isOpen() {
+        return springResource.isOpen();
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull public URL getURL() throws IOException {
+        return springResource.getURL();
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull public URI getURI() throws IOException {
+        return springResource.getURI();
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull public File getFile() throws IOException {
+        return springResource.getFile();
+    }
+
+    /** {@inheritDoc} */
+    @Override public long contentLength() throws IOException {
+        return springResource.contentLength();
+    }
+
+    /** {@inheritDoc} */
+    @Override public long lastModified() throws IOException {
+        return springResource.lastModified();
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull public net.shibboleth.shared.resource.Resource createRelativeResource(
+            @Nonnull String relativePath) throws IOException {
+
+        return of(springResource.createRelative(relativePath));
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getFilename() {
+        return springResource.getFilename();
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull public String getDescription() {
+        return springResource.getDescription();
+    }
+
+}
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/ValidateCachedAuthenticationTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/ValidateCachedAuthenticationTest.java
new file mode 100644
index 0000000..6585158
--- /dev/null
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/ValidateCachedAuthenticationTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.authn.impl;
+
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.net.UrlEscapers;
+
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.net.CookieManager;
+import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.shared.resource.Resource;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.DataSealerException;
+import net.shibboleth.shared.security.impl.BasicKeystoreKeyStrategy;
+import net.shibboleth.sp.context.AgentRequestContext;
+import net.shibboleth.sp.impl.BasicAgent;
+
+/**
+ * Unit test for {@link ValidateCachedAuthentication} action.
+ */
+ at SuppressWarnings("javadoc")
+public class ValidateCachedAuthenticationTest {
+
+    @Nonnull @NotEmpty protected static final String COOKIE_NAME = "_cookieName";
+
+    private Resource keystoreResource;
+    private Resource versionResource;
+    private CookieManager cookieManager;
+    private DataSealer dataSealer;
+
+    private MockHttpServletRequest request;
+    private MockHttpServletResponse response;
+    
+    private RequestContext src;
+    private ProfileRequestContext prc;
+    private BasicAgent agent;
+    private ValidateCachedAuthentication action;
+    
+    private DataSealer createDataSealer(@Nullable @NotEmpty final String nodePrefix)
+            throws DataSealerException, ComponentInitializationException {
+        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.setNodePrefix(nodePrefix);
+        sealer.initialize();
+        return sealer;
+    }
+    
+    @BeforeClass
+    public void beforeClass() throws DataSealerException, ComponentInitializationException {
+        ClassPathResource resource =
+                new ClassPathResource("/net/shibboleth/sp/authn/impl/SealerKeyStore.jks");
+        Assert.assertTrue(resource.exists());
+        keystoreResource = TestResourceConverter.of(resource);
+
+        resource =
+                new ClassPathResource("/net/shibboleth/sp/authn/impl/SealerKeyStore.kver");
+        Assert.assertTrue(resource.exists());
+        versionResource = TestResourceConverter.of(resource);
+        
+        dataSealer = createDataSealer(null);
+    }
+    
+    @BeforeMethod
+    public void setUp() throws ComponentInitializationException {
+        src = new RequestContextBuilder().buildRequestContext();
+        prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+        prc.ensureSubcontext(AuthenticationContext.class);
+
+        request = new MockHttpServletRequest();
+        request.setRemoteAddr("127.0.0.1");
+        response = new MockHttpServletResponse();
+        
+        cookieManager = new CookieManager();
+        cookieManager.setHttpServletRequestSupplier(new NonnullSupplier<>() { @Nonnull public HttpServletRequest get() {return request;}});
+        cookieManager.setHttpServletResponseSupplier(new NonnullSupplier<>() { @Nonnull public HttpServletResponse get() {return response;}});
+        cookieManager.initialize();
+        
+        agent = new BasicAgent();
+        agent.setId("foo");
+        agent.initialize();
+        
+        action = new ValidateCachedAuthentication();
+        action.setHttpServletRequestSupplier(new NonnullSupplier<>() { @Nonnull public HttpServletRequest get() {return request;}});
+        action.setCookieName(COOKIE_NAME);
+        action.setCookieManager(cookieManager);
+        action.setDataSealer(dataSealer);
+        action.initialize();
+    }
+    @Test
+    public void testNoAgent() {
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertEvent(event, EventIds.INVALID_PROFILE_CTX);
+    }
+    
+    @Test
+    public void testNoCaching() throws ComponentInitializationException {
+        agent = new BasicAgent();
+        agent.setId("foo");
+        agent.setSupportsCachedAuthentication(false);
+        agent.initialize();
+        prc.ensureSubcontext(AgentRequestContext.class).setAgent(agent);
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testNoCookie() throws ComponentInitializationException {
+        prc.ensureSubcontext(AgentRequestContext.class).setAgent(agent);
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+    
+    @Test
+    public void testBadCookie() throws ComponentInitializationException {
+        prc.ensureSubcontext(AgentRequestContext.class).setAgent(agent);
+        
+        request.setCookies(new Cookie(COOKIE_NAME, "zork"));
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testExoiredCookie() throws ComponentInitializationException, DataSealerException {
+        prc.ensureSubcontext(AgentRequestContext.class).setAgent(agent);
+        
+        request.setCookies(buildCookie(agent.getId(), "127.0.0.1", Instant.now().minusSeconds(30)));
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testWrongAgent() throws ComponentInitializationException, DataSealerException {
+        prc.ensureSubcontext(AgentRequestContext.class).setAgent(agent);
+        
+        request.setCookies(buildCookie("wrong", "127.0.0.1", Instant.now().plusSeconds(3600)));
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testWrongAddress() throws ComponentInitializationException, DataSealerException {
+        prc.ensureSubcontext(AgentRequestContext.class).setAgent(agent);
+        
+        request.setCookies(buildCookie(agent.getId(), "127.0.0.2", Instant.now().plusSeconds(3600)));
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testWrongFormat() throws ComponentInitializationException, DataSealerException {
+        prc.ensureSubcontext(AgentRequestContext.class).setAgent(agent);
+        
+        request.setCookies(buildCookie(null, "127.0.0.1", Instant.now().plusSeconds(3600)));
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testSuccess() throws ComponentInitializationException, DataSealerException {
+        prc.ensureSubcontext(AgentRequestContext.class).setAgent(agent);
+        
+        request.setCookies(buildCookie(agent.getId(), "127.0.0.1", Instant.now().plusSeconds(3600)));
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertEvent(event, ValidateCachedAuthentication.BYPASS_AUTHENTICATION);
+    }
+
+    /**
+     * Build a sealed cookie to order.
+     * 
+     * @param agentId agent ID
+     * @param address client adddress
+     * @param expires data expiration
+     * 
+     * @return the cookie
+     * 
+     * @throws DataSealerException if sealer fails
+     */
+    @Nonnull private Cookie buildCookie(@Nullable final String agentId, @Nullable final String address,
+            @Nonnull final Instant expires) throws DataSealerException {
+        
+        final StringBuilder builder = new StringBuilder();
+        if (agentId != null) {
+            builder.append(agentId);
+            if (address != null) {
+                builder.append('!').append(address);
+            }
+        } else if (address != null) {
+            builder.append(address);
+        }
+        
+        final String wrapped = dataSealer.wrap(builder.toString(), expires);
+        return new Cookie(COOKIE_NAME, UrlEscapers.urlFormParameterEscaper().escape(wrapped));
+    }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/test/resources/net/shibboleth/sp/authn/impl/SealerKeyStore.jks b/sp-server-impl/src/test/resources/net/shibboleth/sp/authn/impl/SealerKeyStore.jks
new file mode 100644
index 0000000..147d92b
Binary files /dev/null and b/sp-server-impl/src/test/resources/net/shibboleth/sp/authn/impl/SealerKeyStore.jks differ
diff --git a/sp-server-impl/src/test/resources/net/shibboleth/sp/authn/impl/SealerKeyStore.kver b/sp-server-impl/src/test/resources/net/shibboleth/sp/authn/impl/SealerKeyStore.kver
new file mode 100644
index 0000000..2cd48df
--- /dev/null
+++ b/sp-server-impl/src/test/resources/net/shibboleth/sp/authn/impl/SealerKeyStore.kver
@@ -0,0 +1 @@
+CurrentVersion = 1

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


More information about the commits mailing list