[java-identity-provider] branch main updated: IDP-1655 Implement encoded proxy-granting tickets.

Marvin S. Addison marvin.addison at gmail.com
Wed Sep 30 20:03:25 UTC 2020


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

serac pushed a commit to branch main
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=597da818861c0b87f82d75f84746a93eaf21df09

The following commit(s) were added to refs/heads/main by this push:
       new  597da8188 IDP-1655 Implement encoded proxy-granting tickets.
597da8188 is described below

commit 597da818861c0b87f82d75f84746a93eaf21df09
Author: Marvin S. Addison <serac at vt.edu>
AuthorDate: Wed Sep 30 10:48:40 2020 -0400

    IDP-1655 Implement encoded proxy-granting tickets.
    
    Implement data-sealer-based encoding for root proxy-granting tickets, while
    delegating subsequent proxy-granting tickets in in a chain to durable
    storage via configured StorageService. This solution is expected to cover
    99% of CAS proxy use cases.
    
    Based on a contributed patch from Paul B. Henson, henson at acm.org.
---
 .../idp/cas/flow/impl/BuildProxyChainAction.java   |  5 +-
 .../cas/flow/impl/ValidateProxyCallbackAction.java | 27 +++++----
 .../idp/cas/flow/impl/ValidateTicketAction.java    |  2 +-
 .../idp/cas/ticket/impl/EncodingTicketService.java | 70 +++++++++++++++++++---
 .../idp/cas/flow/impl/AbstractFlowActionTest.java  | 31 ++++++++++
 .../cas/flow/impl/BuildProxyChainActionTest.java   | 13 ++--
 .../cas/flow/impl/ValidateTicketActionTest.java    | 12 +++-
 .../cas/ticket/impl/EncodingTicketServiceTest.java | 25 ++++++++
 .../src/test/resources/spring/test-flow-beans.xml  | 11 +++-
 .../shibboleth/idp/conf/cas-protocol-system.xml    |  1 +
 .../shibboleth/idp/module/conf/cas-protocol.xml    | 18 +++---
 idp-conf/src/main/resources/conf/idp.properties    |  4 +-
 .../idp/test/flows/cas/ProxyValidateFlowTest.java  | 39 ++++++++++--
 13 files changed, 209 insertions(+), 49 deletions(-)

diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/BuildProxyChainAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/BuildProxyChainAction.java
index bb2649442..1edc69783 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/BuildProxyChainAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/BuildProxyChainAction.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.cas.flow.impl;
 
+import java.time.Instant;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
@@ -99,8 +100,8 @@ public class BuildProxyChainAction
         String pgtId = pt.getPgtId();
         do {
             pgt = casTicketService.fetchProxyGrantingTicket(pgtId);
-            if (pgt == null) {
-                log.debug("{} PGT {} not found", getLogPrefix(), pgtId);
+            if (pgt == null || Instant.now().isAfter(pgt.getExpirationInstant())) {
+                log.debug("{} PGT {} {}", getLogPrefix(), pgtId, pgt == null ? "not found" : "expired");
                 ActionSupport.buildEvent(profileRequestContext, ProtocolError.BrokenProxyChain.event(this));
                 return;
             }
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java
index 5e4cfbbd3..4808ed299 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java
@@ -24,6 +24,7 @@ import java.time.Instant;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import net.shibboleth.idp.cas.ticket.ProxyGrantingTicket;
 import org.apache.http.client.utils.URIBuilder;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventException;
@@ -138,11 +139,19 @@ public class ValidateProxyCallbackAction
 
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
-        final IdentifierGenerationStrategy pgtGenerator = validateConfig.getPGTIOUGenerator(profileRequestContext);
-        final ProxyIdentifiers proxyIds = new ProxyIdentifiers(
-                securityConfig.getIdGenerator().generateIdentifier(),
-                pgtGenerator.generateIdentifier());
+        final IdentifierGenerationStrategy pgtGenerator = securityConfig.getIdGenerator();
+        final IdentifierGenerationStrategy pgtIOUGenerator = validateConfig.getPGTIOUGenerator(profileRequestContext);
+        final Instant expiration = Instant.now().plus(validateConfig.getTicketValidityPeriod(profileRequestContext));
+        final String pgtId = pgtGenerator.generateIdentifier();
+        final ProxyGrantingTicket pgt;
+        if (ticket instanceof ServiceTicket) {
+            pgt = casTicketService.createProxyGrantingTicket(pgtId, expiration, (ServiceTicket) ticket);
+        } else {
+            pgt = casTicketService.createProxyGrantingTicket(pgtId, expiration, (ProxyTicket) ticket);
+        }
+        // The ID of the proxy-granting ticket MAY be different from the generated value above.
+        // ALWAYS use the value from the ticket object.
+        final ProxyIdentifiers proxyIds = new ProxyIdentifiers(pgt.getId(), pgtIOUGenerator.generateIdentifier());
         final URI proxyCallbackUri;
         try {
             proxyCallbackUri = new URIBuilder(request.getPgtUrl())
@@ -158,16 +167,10 @@ public class ValidateProxyCallbackAction
         try {
             log.debug("{} Attempting proxy authentication to {}", getLogPrefix(), proxyCallbackUri);
             proxyValidator.validate(profileRequestContext, proxyCallbackUri);
-            final Instant expiration =
-                    Instant.now().plus(validateConfig.getTicketValidityPeriod(profileRequestContext));
-            if (ticket instanceof ServiceTicket) {
-                casTicketService.createProxyGrantingTicket(proxyIds.getPgtId(), expiration, (ServiceTicket) ticket);
-            } else {
-                casTicketService.createProxyGrantingTicket(proxyIds.getPgtId(), expiration, (ProxyTicket) ticket);
-            }
             response.setPgtIou(proxyIds.getPgtIou());
         } catch (final Exception e) {
             log.warn("{} Proxy authentication failed for {}", getLogPrefix(), request.getPgtUrl(), e);
+            casTicketService.removeProxyGrantingTicket(pgt.getId());
             ActionSupport.buildEvent(profileRequestContext,
                     ProtocolError.ProxyCallbackAuthenticationFailure.event(this));
         }
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateTicketAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateTicketAction.java
index e534f9f07..ea91a12c8 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateTicketAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateTicketAction.java
@@ -129,7 +129,7 @@ public class ValidateTicketAction extends AbstractCASProtocolAction<TicketValida
             return;
         }
 
-        if (ticket == null || ticket.getExpirationInstant().isBefore(Instant.now())) {
+        if (ticket == null || Instant.now().isAfter(ticket.getExpirationInstant())) {
             ActionSupport.buildEvent(profileRequestContext, ProtocolError.TicketExpired.event(this));
             return;
         }
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/ticket/impl/EncodingTicketService.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/ticket/impl/EncodingTicketService.java
index 2408e1871..2c713f949 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/ticket/impl/EncodingTicketService.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/ticket/impl/EncodingTicketService.java
@@ -40,15 +40,16 @@ import org.slf4j.LoggerFactory;
  * Ticket service that uses two different strategies for ticket persistence:
  *
  * <ol>
- *     <li>Service tickets and proxy tickets are persisted by serializing ticket data and encrypting it into the opaque
- *     part of the ticket ID using a {@link DataSealer}.</li>
- *     <li>Proxy-granting tickets are persisted using a {@link StorageService}.</li>
+ *     <li>Service tickets, proxy tickets, and root proxy-granting tickets are persisted by serializing
+ *     ticket data and encrypting it into the opaque part of the ticket ID using a {@link DataSealer}.</li>
+ *     <li>Chained proxy-granting tickets are persisted using a {@link StorageService}.</li>
  * </ol>
  *
- * <p><strong>NOTE:</strong> The service tickets and proxy tickets produced by this component do not support one-time
- * use. More precisely, {@link #removeServiceTicket(String)} and {@link #removeProxyTicket(String)} simply return a
- * decoded ticket and do not invalidate the ticket in any way. Since there is no backing store for those types of
- * tickets, they can be reused until one of the following conditions is met:
+ * <p><strong>NOTE:</strong> The service tickets, proxy tickets, and root proxy-granting tickets produced by
+ * this component do not support one-time use. More precisely, {@link #removeServiceTicket(String)} and
+ * {@link #removeProxyTicket(String)} simply return a decoded ticket and do not invalidate the ticket in any way.
+ * Since there is no backing store for those types of  tickets, they can be reused until one of the following
+ * conditions is met:
  *
  * <ol>
  *     <li>The value of {@link Ticket#getExpirationInstant()} is exceeded.</li>
@@ -56,6 +57,7 @@ import org.slf4j.LoggerFactory;
  * </ol>
  *
  * @author Marvin S. Addison
+ * @author Paul B. Henson
  * @since 3.3.0
  */
 public class EncodingTicketService extends AbstractTicketService {
@@ -66,6 +68,9 @@ public class EncodingTicketService extends AbstractTicketService {
     /** Default proxy ticket prefix. */
     public static final String PROXY_TICKET_PREFIX = "PT";
 
+    /** Default proxy granting ticket prefix. */
+    public static final String PROXY_GRANTING_TICKET_PREFIX = "PGT-E";
+
     /** Non-null marker value for unused ServiceTicket#id field and storage context name. */
     private static final String NOT_USED = "na";
 
@@ -84,6 +89,9 @@ public class EncodingTicketService extends AbstractTicketService {
     @NotEmpty
     private String proxyTicketPrefix = PROXY_TICKET_PREFIX;
 
+    /** Proxy granting ticket prefix. */
+    @NotEmpty
+    private String proxyGrantingTicketPrefix = PROXY_GRANTING_TICKET_PREFIX;
 
     /**
      * Creates a new instance.
@@ -107,7 +115,7 @@ public class EncodingTicketService extends AbstractTicketService {
     }
 
     /**
-     * Sets the proxy ticket prefix. Default is PGT.
+     * Sets the proxy ticket prefix. Default is PT.
      *
      * @param prefix Proxy ticket prefix.
      */
@@ -115,6 +123,16 @@ public class EncodingTicketService extends AbstractTicketService {
         proxyTicketPrefix = Constraint.isNotEmpty(prefix, "Prefix cannot be null or empty");
     }
 
+    /**
+     * Sets the proxy granting ticket prefix. Default is PGT-E. Note that this MUST be distinct from
+     * the proxy granting ticket prefix used for regular proxy-granting ticket identifiers.
+     *
+     * @param prefix Proxy granting ticket prefix.
+     */
+    public void setProxyGrantingTicketPrefix(final String prefix) {
+        proxyGrantingTicketPrefix = Constraint.isNotEmpty(prefix, "Prefix cannot be null or empty");
+    }
+
     @Override
     @Nonnull
     public ServiceTicket createServiceTicket(
@@ -163,6 +181,42 @@ public class EncodingTicketService extends AbstractTicketService {
         return decode(ProxyTicket.class, id, proxyTicketPrefix);
     }
 
+    @Nullable
+    @Override
+    public ProxyGrantingTicket createProxyGrantingTicket(
+            @Nonnull final String id,
+            @Nonnull final Instant expiry,
+            @Nonnull final ServiceTicket serviceTicket) {
+        Constraint.isNotNull(serviceTicket, "ServiceTicket cannot be null");
+        final ProxyGrantingTicket pgt = new ProxyGrantingTicket(
+                NOT_USED,
+                serviceTicket.getService(),
+                Constraint.isNotNull(expiry, "Expiry cannot be null"),
+                null);
+        pgt.setTicketState(serviceTicket.getTicketState());
+        return encode(ProxyGrantingTicket.class, pgt, proxyGrantingTicketPrefix);
+    }
+
+    @Nullable
+    @Override
+    public ProxyGrantingTicket fetchProxyGrantingTicket(@Nonnull final String id) {
+        Constraint.isNotNull(id, "Id cannot be null");
+        if (id.startsWith(proxyGrantingTicketPrefix + "-")) {
+            return decode(ProxyGrantingTicket.class, id, proxyGrantingTicketPrefix);
+        }
+        return super.fetchProxyGrantingTicket(id);
+    }
+
+    @Override
+    @Nullable
+    public ProxyGrantingTicket removeProxyGrantingTicket(@Nonnull final String id) {
+        Constraint.isNotNull(id, "Id cannot be null");
+        if (id.startsWith(proxyGrantingTicketPrefix + "-")) {
+            return decode(ProxyGrantingTicket.class, id, proxyGrantingTicketPrefix);
+        }
+        return super.removeProxyGrantingTicket(id);
+    }
+
     /**
      * Encode a ticket.
      * 
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/AbstractFlowActionTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/AbstractFlowActionTest.java
index 8217e9bf1..5f760ddab 100644
--- a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/AbstractFlowActionTest.java
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/AbstractFlowActionTest.java
@@ -17,10 +17,15 @@
 
 package net.shibboleth.idp.cas.flow.impl;
 
+import java.security.KeyException;
+import java.security.SecureRandom;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.HashSet;
 
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.SecretKeySpec;
 import net.shibboleth.idp.authn.AuthenticationResult;
 import net.shibboleth.idp.cas.ticket.ProxyGrantingTicket;
 import net.shibboleth.idp.cas.ticket.ProxyTicket;
@@ -31,6 +36,8 @@ import net.shibboleth.idp.cas.ticket.TicketState;
 import net.shibboleth.idp.session.IdPSession;
 import net.shibboleth.idp.session.SessionException;
 import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.security.DataSealerKeyStrategy;
 import org.opensaml.core.config.InitializationException;
 import org.opensaml.core.config.InitializationService;
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -137,4 +144,28 @@ public abstract class AbstractFlowActionTest extends AbstractTestNGSpringContext
     public void initOpenSAML() throws InitializationException {
         InitializationService.initialize();
     }
+
+    /**
+     * Test implementation of {@link DataSealerKeyStrategy} that emits a static key for all inquiries.
+     */
+    public static class MockDataSealerKeyStrategy implements DataSealerKeyStrategy {
+        /** Static key. */
+        private final SecretKey key;
+
+        public MockDataSealerKeyStrategy() {
+            final byte[] bytes = new byte[32];
+            new SecureRandom().nextBytes(bytes);
+            key = new SecretKeySpec(bytes, "AES");
+        }
+
+        @Override
+        public Pair<String, SecretKey> getDefaultKey() throws KeyException {
+            return new Pair<>("default", key);
+        }
+
+        @Override
+        public SecretKey getKey(final String s) throws KeyException {
+            return key;
+        }
+    }
 }
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/BuildProxyChainActionTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/BuildProxyChainActionTest.java
index 5ca14d0c4..da03ba1d6 100644
--- a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/BuildProxyChainActionTest.java
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/BuildProxyChainActionTest.java
@@ -66,15 +66,20 @@ public class BuildProxyChainActionTest extends AbstractFlowActionTest {
         final ProxyTicket ptA = createProxyTicket(pgtA, "proxiedByA");
         final ProxyGrantingTicket pgtB = createProxyGrantingTicket(ptA);
         final ProxyTicket ptB = createProxyTicket(pgtB, "proxiedByB");
-        final TicketValidationRequest request = new TicketValidationRequest("proxiedByB", ptB.getId());
+        final ProxyGrantingTicket pgtC = createProxyGrantingTicket(ptB);
+        final ProxyTicket ptC = createProxyTicket(pgtC, "proxiedByC");
+        final TicketValidationRequest request = new TicketValidationRequest("proxiedByC", ptC.getId());
         final TicketValidationResponse response = new TicketValidationResponse();
 
-        // Remove first proxy-granting ticket to break chain
-        ticketService.removeProxyGrantingTicket(pgtA.getId());
+        // Remove second proxy-granting ticket to break chain
+        // NOTE: Cannot remove root PGT when using EncodingTicketService because there's nothing to remove.
+        // We use a chain of 3 here so that we can remove the second link, which should pass regardless of
+        // TicketService implementation.
+        ticketService.removeProxyGrantingTicket(pgtB.getId());
 
         final RequestContext context = new TestContextBuilder(ProxyConfiguration.PROFILE_ID)
                 .addProtocolContext(request, response)
-                .addTicketContext(ptB)
+                .addTicketContext(ptC)
                 .build();
         assertEquals(action.execute(context).getId(), ProtocolError.BrokenProxyChain.name());
     }
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/ValidateTicketActionTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/ValidateTicketActionTest.java
index 11848bc79..db4992fb4 100644
--- a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/ValidateTicketActionTest.java
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/ValidateTicketActionTest.java
@@ -17,6 +17,8 @@
 
 package net.shibboleth.idp.cas.flow.impl;
 
+import java.time.Duration;
+import java.time.Instant;
 import net.shibboleth.idp.cas.config.ValidateConfiguration;
 import net.shibboleth.idp.cas.protocol.ProtocolError;
 import net.shibboleth.idp.cas.protocol.TicketValidationRequest;
@@ -24,6 +26,7 @@ import net.shibboleth.idp.cas.ticket.ProxyGrantingTicket;
 import net.shibboleth.idp.cas.ticket.ProxyTicket;
 import net.shibboleth.idp.cas.ticket.ServiceTicket;
 import net.shibboleth.idp.cas.ticket.TicketService;
+import net.shibboleth.idp.cas.ticket.TicketState;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import org.springframework.webflow.execution.RequestContext;
 import org.testng.annotations.Test;
@@ -62,13 +65,16 @@ public class ValidateTicketActionTest extends AbstractFlowActionTest {
 
     @Test
     public void testTicketExpired() throws Exception {
-        final ServiceTicket ticket = createServiceTicket(TEST_SERVICE, false);
+        final int ticketTTLMillis = 10;
+        final TicketState state = new TicketState(TEST_SESSION_ID, TEST_PRINCIPAL_NAME, Instant.now(), "Password");
+        final ServiceTicket ticket = ticketService.createServiceTicket(
+            generateServiceTicketId(), Instant.now().plusMillis(ticketTTLMillis), TEST_SERVICE, state, false);
         final RequestContext context = new TestContextBuilder(ValidateConfiguration.PROFILE_ID)
                 .addProtocolContext(new TicketValidationRequest(TEST_SERVICE, ticket.getId()), null)
                 .addRelyingPartyContext(ticket.getService(), true, new ValidateConfiguration())
                 .build();
-        // Remove the ticket prior to validation to simulate expiration
-        ticketService.removeServiceTicket(ticket.getId());
+        // Wait briefly to let ticket expire
+        Thread.sleep(ticketTTLMillis + 5);
         assertEquals(newAction(ticketService).execute(context).getId(), ProtocolError.TicketExpired.name());
     }
 
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/ticket/impl/EncodingTicketServiceTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/ticket/impl/EncodingTicketServiceTest.java
index 0e076c923..d27defe80 100644
--- a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/ticket/impl/EncodingTicketServiceTest.java
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/ticket/impl/EncodingTicketServiceTest.java
@@ -166,6 +166,31 @@ public class EncodingTicketServiceTest {
         assertNull(ticketService.removeProxyTicket("PT-123"));
     }
 
+    @Test
+    public void testCreateFetchRemoveEncodedProxyGrantingTicket() {
+        final String principal = "aleph";
+        final String serviceUrl = "https://www.example.com/pgt1/";
+        final ServiceTicket st = ticketService.createServiceTicket(
+            String.valueOf(System.currentTimeMillis()),
+            Instant.now().plusSeconds(5),
+            serviceUrl,
+            newState(principal),
+            true);
+        final Instant expiry = Instant.now().plusSeconds(3600);
+        final ProxyGrantingTicket pgt = ticketService.createProxyGrantingTicket("notused", expiry, st);
+        assertTrue(pgt.getId().startsWith("PGT-E-"));
+        final ProxyGrantingTicket pgt2 = ticketService.fetchProxyGrantingTicket(pgt.getId());
+        assertNotNull(pgt2);
+        assertEquals(pgt2.getService(), serviceUrl);
+        assertEquals(pgt2.getTicketState().getPrincipalName(), principal);
+        final ProxyGrantingTicket pgt3 = ticketService.removeProxyGrantingTicket(pgt.getId());
+        assertNotNull(pgt3);
+        assertEquals(pgt3.getService(), serviceUrl);
+        assertEquals(pgt3.getTicketState().getPrincipalName(), principal);
+        // Removing encoded tickets is the same as fetching so they are still available (no backing storage)
+        assertNotNull(ticketService.fetchProxyGrantingTicket(pgt.getId()));
+    }
+
     private TicketState newState(final String principal) {
         return new TicketState(sessionIdGenerator.generateIdentifier(), principal,
                 Instant.now().truncatedTo(ChronoUnit.MILLIS), "authn/Password");
diff --git a/idp-cas-impl/src/test/resources/spring/test-flow-beans.xml b/idp-cas-impl/src/test/resources/spring/test-flow-beans.xml
index 7abd56156..cbd12a67a 100644
--- a/idp-cas-impl/src/test/resources/spring/test-flow-beans.xml
+++ b/idp-cas-impl/src/test/resources/spring/test-flow-beans.xml
@@ -62,8 +62,15 @@
           p:IDGenerator-ref="shibboleth.SessionIDGenerator" />
 
     <bean id="shibboleth.CASTicketService"
-          class="net.shibboleth.idp.cas.ticket.impl.SimpleTicketService"
-          c:service-ref="shibboleth.StorageService" />
+          class="net.shibboleth.idp.cas.ticket.impl.EncodingTicketService"
+          c:service-ref="shibboleth.StorageService"
+          c:sealer-ref="encodedTicketSealer" />
+
+    <bean id="encodedTicketSealer" lazy-init="true"
+          class="net.shibboleth.utilities.java.support.security.DataSealer"
+          p:keyStrategy-ref="keyStrategy" />
+
+    <bean id="keyStrategy" class="net.shibboleth.idp.cas.flow.impl.AbstractFlowActionTest.MockDataSealerKeyStrategy" />
 
     <bean id="shibboleth.CASServiceRegistry"
           class="net.shibboleth.idp.cas.service.PatternServiceRegistry">
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/cas-protocol-system.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/cas-protocol-system.xml
index adbc435d8..77fe39379 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/cas-protocol-system.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/cas-protocol-system.xml
@@ -34,6 +34,7 @@
           c:useHex="false"
           c:pad="#{ T(java.lang.Integer).valueOf(45).byteValue() }" />
 
+    <!-- TODO: Change to encodingTicketService for 5.0 -->
     <alias name="simpleTicketService" alias="shibboleth.DefaultCASTicketService" />
 
     <bean id="proxyHttpClient" parent="shibboleth.InternalHttpClient"
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/cas-protocol.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/cas-protocol.xml
index b73a972b3..fde431e47 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/cas-protocol.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/cas-protocol.xml
@@ -48,14 +48,14 @@
 
     <!--
        | The default ticket service serializes ticket data into the opaque section of the ticket ID for
-       | service tickets and proxy tickets. Proxy-granting tickets still require server-side storage, and a
-       | StorageService defined by the idp.cas.StorageService is used. Thus for deployers that do not require
-       | CAS proxy capabilities, no stateful storage mechanism is required; that means no memcached or database
-       | is required for HA deployments that want CAS (without proxy) support. A notable limitation of the new
-       | component is that the one-time use feature of service and proxy tickets is not available due to the lack
-       | of a ticket-tracking mechanism. Instead, tickets expire when their expiration period is exceeded.
-       | If this limitation is of concern, one may consider decreasing ticketValidityPeriod on the profile
-       | configuration from the default 15000ms.
+       | service tickets, proxy tickets, and root proxy-granting tickets. Chained proxy-granting tickets still
+       | require server-side storage, and a StorageService defined by the idp.cas.StorageService is used. Thus
+       | for deployers that do not require chained CAS proxy capabilities, no stateful storage mechanism is
+       | required; that means no memcached or database is required for HA deployments that want CAS (without
+       | chained proxy) support. A notable limitation of the new component is that the one-time use feature of
+       | service and proxy tickets is not available due to the lack of a ticket-tracking mechanism. Instead,
+       | tickets expire when their expiration period is exceeded. If this limitation is of concern, one may
+       | consider decreasing ticketValidityPeriod on the profile configuration from the default 15000ms.
        -->
     <alias name="encodingTicketService" alias="shibboleth.CASTicketService" />
 
@@ -103,4 +103,4 @@
     <bean id="shibboleth.CASTicketService"
           class="org.example.idp.cas.CustomTicketService" />
     -->
-</beans>
\ No newline at end of file
+</beans>
diff --git a/idp-conf/src/main/resources/conf/idp.properties b/idp-conf/src/main/resources/conf/idp.properties
index c42c09d68..ec8e8eb9d 100644
--- a/idp-conf/src/main/resources/conf/idp.properties
+++ b/idp-conf/src/main/resources/conf/idp.properties
@@ -198,7 +198,7 @@ idp.session.secondaryServiceIndex = true
 # browser-supported languages, defaults to an empty list.
 idp.ui.fallbackLanguages=en,fr,de
 
-# Storage service used by CAS protocol for proxy-granting tickets
+# Storage service used by CAS protocol for chained proxy-granting tickets
 # and when using server-managed "simple" TicketService.
 # Defaults to shibboleth.StorageService (in-memory)
 # MUST be server-side storage (e.g. in-memory, memcached, database)
@@ -218,4 +218,4 @@ idp.ui.fallbackLanguages=en,fr,de
 #idp.fticks.logport=514
 
 # Set false if you want SAML bindings "spelled out" in audit log
-idp.audit.shortenBindings = true
\ No newline at end of file
+idp.audit.shortenBindings = true
diff --git a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ProxyValidateFlowTest.java b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ProxyValidateFlowTest.java
index 13463a026..ebc52c707 100644
--- a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ProxyValidateFlowTest.java
+++ b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ProxyValidateFlowTest.java
@@ -25,6 +25,7 @@ import net.shibboleth.idp.cas.ticket.ServiceTicket;
 import net.shibboleth.idp.cas.ticket.TicketIdentifierGenerationStrategy;
 import net.shibboleth.idp.cas.ticket.TicketService;
 import net.shibboleth.idp.cas.ticket.TicketState;
+import net.shibboleth.idp.cas.ticket.impl.EncodingTicketService;
 import net.shibboleth.idp.session.IdPSession;
 import net.shibboleth.idp.session.SessionManager;
 import net.shibboleth.idp.test.flows.AbstractFlowTest;
@@ -33,6 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.SkipException;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.*;
@@ -142,13 +144,38 @@ public class ProxyValidateFlowTest extends AbstractFlowTest {
     @Test
     public void testFailureBrokenProxyChain() throws Exception {
         final String principal = "john";
+        final int pgtTTLMillis = 20;
         final IdPSession session = sessionManager.createSession(principal);
-        final ProxyTicket ticket = createProxyTicket(session.getId(), principal);
-
-        ticketService.removeProxyGrantingTicket(ticket.getPgtId());
-
-        externalContext.getMockRequestParameterMap().put("service", ticket.getService());
-        externalContext.getMockRequestParameterMap().put("ticket", ticket.getId());
+        final ServiceTicket st = ticketService.createServiceTicket(
+            new TicketIdentifierGenerationStrategy("ST", 25).generateIdentifier(),
+            Instant.now().plusSeconds(5),
+            "https://service.example.org/",
+            new TicketState(session.getId(), principal, Instant.now(), "Password"),
+            false);
+        final ProxyGrantingTicket pgt1 = ticketService.createProxyGrantingTicket(
+            new TicketIdentifierGenerationStrategy("PGT", 50).generateIdentifier(),
+            Instant.now().plusMillis(pgtTTLMillis),
+            st);
+        final ProxyTicket pt1 = ticketService.createProxyTicket(
+            new TicketIdentifierGenerationStrategy("PT", 25).generateIdentifier(),
+            Instant.now().plusSeconds(5),
+            pgt1,
+            "https://proxy1.example.org/");
+        final ProxyGrantingTicket pgt2 = ticketService.createProxyGrantingTicket(
+            new TicketIdentifierGenerationStrategy("PGT", 50).generateIdentifier(),
+            Instant.now().plusSeconds(3600),
+            pt1);
+        final ProxyTicket pt2 = ticketService.createProxyTicket(
+            new TicketIdentifierGenerationStrategy("PT", 25).generateIdentifier(),
+            Instant.now().plusSeconds(5),
+            pgt2,
+            "https://proxy2.example.org/");
+
+        externalContext.getMockRequestParameterMap().put("service", pt2.getService());
+        externalContext.getMockRequestParameterMap().put("ticket", pt2.getId());
+
+        // Wait for PGT#1 to expire
+        Thread.sleep(pgtTTLMillis + 5);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
 

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


More information about the commits mailing list