[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