[java-identity-provider] branch main updated: IDP-1919 - CAS validation flows don't run consent checking interceptor
Scott Cantor
cantor.2 at osu.edu
Wed Mar 16 17:16:27 UTC 2022
This is an automated email from the git hooks/post-receive script.
scantor 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=f38ca927cd6d76dcbaf5523f828f517b1a330a12
The following commit(s) were added to refs/heads/main by this push:
new f38ca927c IDP-1919 - CAS validation flows don't run consent checking interceptor
f38ca927c is described below
commit f38ca927cd6d76dcbaf5523f828f517b1a330a12
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Mar 16 13:16:23 2022 -0400
IDP-1919 - CAS validation flows don't run consent checking interceptor
https://shibboleth.atlassian.net/browse/IDP-1919
Shore up by optionally storing consented IDs in TicketState.
---
.../idp/attribute/context/AttributeContext.java | 10 ++--
.../idp/cas/config/LoginConfiguration.java | 39 ++++++++++++++
.../net/shibboleth/idp/cas/ticket/TicketState.java | 60 ++++++++++++++++------
.../cas/flow/impl/GrantServiceTicketAction.java | 49 +++++++++++++++++-
.../PrepareTicketValidationResponseAction.java | 16 +++++-
.../impl/AbstractTicketSerializer.java | 48 +++++++++++++----
.../impl/ServiceTicketSerializerTest.java | 43 ++++++++++++++++
.../shibboleth/idp/conf/relying-party-mddriven.xml | 8 +++
.../idp/test/flows/cas/SamlValidateFlowTest.java | 34 ++++++++++++
.../test/flows/cas/ServiceValidateFlowTest.java | 42 ++++++++++++++-
10 files changed, 314 insertions(+), 35 deletions(-)
diff --git a/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/context/AttributeContext.java b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/context/AttributeContext.java
index bd5de7157..ec47185ea 100644
--- a/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/context/AttributeContext.java
+++ b/idp-attribute-api/src/main/java/net/shibboleth/idp/attribute/context/AttributeContext.java
@@ -121,12 +121,9 @@ public final class AttributeContext extends BaseContext {
}
/**
- * Gets whether attribute release consent was obtained from the subject during this request.
+ * Gets whether attribute release consent was obtained from the subject during this request but not stored.
*
- * <p>This may be false if consent was obtained during a prior request, but is a signal to later
- * actions that consent may have been obtained but not stored.</p>
- *
- * @return true iff consent was obtained during this request
+ * @return true iff consent was obtained during this request but not stored
*
* @since 4.2.0
*/
@@ -135,7 +132,7 @@ public final class AttributeContext extends BaseContext {
}
/**
- * Sets whether attribute release consent was obtained from the subject during this request.
+ * Sets whether attribute release consent was obtained from the subject during this request but not stored.
*
* @param flag flag to set
*
@@ -148,4 +145,5 @@ public final class AttributeContext extends BaseContext {
return this;
}
+
}
\ No newline at end of file
diff --git a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/config/LoginConfiguration.java b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/config/LoginConfiguration.java
index c3fb73b09..b5ebdd196 100644
--- a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/config/LoginConfiguration.java
+++ b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/config/LoginConfiguration.java
@@ -74,6 +74,9 @@ public class LoginConfiguration extends AbstractProtocolConfiguration
/** Whether to mandate forced authentication for the request. */
@Nonnull private Predicate<ProfileRequestContext> forceAuthnPredicate;
+ /** Whether to store consent in service tickets. */
+ @Nonnull private Predicate<ProfileRequestContext> storeConsentInTicketsPredicate;
+
/** Lookup function to supply proxyCount property. */
@Nonnull private Function<ProfileRequestContext,Integer> proxyCountLookupStrategy;
@@ -85,6 +88,7 @@ public class LoginConfiguration extends AbstractProtocolConfiguration
postAuthenticationFlowsLookupStrategy = FunctionSupport.constant(null);
defaultAuthenticationContextsLookupStrategy = FunctionSupport.constant(null);
forceAuthnPredicate = Predicates.alwaysFalse();
+ storeConsentInTicketsPredicate = Predicates.alwaysFalse();
proxyCountLookupStrategy = FunctionSupport.constant(null);
}
@@ -215,6 +219,41 @@ public class LoginConfiguration extends AbstractProtocolConfiguration
forceAuthnPredicate = Constraint.isNotNull(condition, "Forced authentication predicate cannot be null");
}
+ /**
+ * Get whether to store consent in service tickets.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return whether to store consent in service tickets
+ *
+ * @since 4.2.0
+ */
+ public boolean isStoreConsentInTickets(@Nullable final ProfileRequestContext profileRequestContext) {
+ return storeConsentInTicketsPredicate.test(profileRequestContext);
+ }
+
+ /**
+ * Set whether to store consent in service tickets.
+ *
+ * @param flag flag to set
+ *
+ * @since 4.2.0
+ */
+ public void setStoreConsentInTickets(final boolean flag) {
+ storeConsentInTicketsPredicate = flag ? Predicates.alwaysTrue() : Predicates.alwaysFalse();
+ }
+
+ /**
+ * Set condition for whether to store consent in service tickets.
+ *
+ * @param condition condition to set
+ *
+ * @since 4.2.0
+ */
+ public void setStoreConsentInTicketsPredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ storeConsentInTicketsPredicate = Constraint.isNotNull(condition, "Condition cannot be null");
+ }
+
/** {@inheritDoc} */
@Nullable public Integer getProxyCount(@Nullable final ProfileRequestContext profileRequestContext) {
final Integer count = proxyCountLookupStrategy.apply(profileRequestContext);
diff --git a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/ticket/TicketState.java b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/ticket/TicketState.java
index c79d4a7cc..4e78db55a 100644
--- a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/ticket/TicketState.java
+++ b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/ticket/TicketState.java
@@ -18,10 +18,18 @@
package net.shibboleth.idp.cas.ticket;
import java.time.Instant;
+import java.util.Collection;
import java.util.Objects;
+import java.util.Set;
+
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
/**
* Supplemental state data to be stored with a ticket.
@@ -31,21 +39,19 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
public class TicketState {
/** ID of session in which ticket is created. */
- @Nonnull
- private String sessId;
+ @Nonnull private String sessId;
/** Canonical authenticated principal name. */
- @Nonnull
- private String authenticatedPrincipalName;
+ @Nonnull private String authenticatedPrincipalName;
/** Authentication instant. */
- @Nonnull
- private Instant authenticationInstant;
+ @Nonnull private Instant authenticationInstant;
/** Authentication method ID/name/description. */
- @Nonnull
- private String authenticationMethod;
+ @Nonnull private String authenticationMethod;
+ /** Attribute IDs that were consented to during the ticket request. */
+ @Nonnull @NonnullElements private Set<String> consentedAttributeIds;
/**
* Creates a new instance with required fields.
@@ -71,8 +77,7 @@ public class TicketState {
*
* @return IdP session ID.
*/
- @Nonnull
- public String getSessionId() {
+ @Nonnull public String getSessionId() {
return sessId;
}
@@ -81,8 +86,7 @@ public class TicketState {
*
* @return Canonical principal.
*/
- @Nonnull
- public String getPrincipalName() {
+ @Nonnull public String getPrincipalName() {
return authenticatedPrincipalName;
}
@@ -91,8 +95,7 @@ public class TicketState {
*
* @return Principal authentication instant.
*/
- @Nonnull
- public Instant getAuthenticationInstant() {
+ @Nonnull public Instant getAuthenticationInstant() {
return authenticationInstant;
}
@@ -101,11 +104,36 @@ public class TicketState {
*
* @return Principal authentication method.
*/
- @Nonnull
- public String getAuthenticationMethod() {
+ @Nonnull public String getAuthenticationMethod() {
return authenticationMethod;
}
+ /**
+ * Get the attribute IDs that were consented to during the request.
+ *
+ * @return immutable set of attribute IDs
+ *
+ * @since 4.2.0
+ */
+ @Nullable @NonnullElements @Unmodifiable @NotLive public Set<String> getConsentedAttributeIds() {
+ return consentedAttributeIds;
+ }
+
+ /**
+ * Set the attribute IDs that were consented to during the request.
+ *
+ * @param attributeIds attribute IDs
+ *
+ * @since 4.2.0
+ */
+ public void setConsentedAttributeIds(@Nullable @NonnullElements final Collection<String> attributeIds) {
+ if (attributeIds != null) {
+ consentedAttributeIds = Set.copyOf(StringSupport.normalizeStringCollection(attributeIds));
+ } else {
+ consentedAttributeIds = null;
+ }
+ }
+
@Override
public boolean equals(final Object o) {
if (o instanceof TicketState) {
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantServiceTicketAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantServiceTicketAction.java
index 3003fff84..8fdd96f78 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantServiceTicketAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantServiceTicketAction.java
@@ -23,6 +23,7 @@ import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import net.shibboleth.idp.attribute.context.AttributeContext;
import net.shibboleth.idp.authn.AuthenticationResult;
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.authn.context.AuthenticationContext;
@@ -38,8 +39,10 @@ import net.shibboleth.idp.cas.ticket.TicketService;
import net.shibboleth.idp.cas.ticket.TicketState;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.config.SecurityConfiguration;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.idp.session.IdPSession;
import net.shibboleth.idp.session.context.SessionContext;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
@@ -75,6 +78,9 @@ public class GrantServiceTicketAction extends AbstractCASProtocolAction<ServiceT
/** Function to retrieve subject principal name. */
@Nonnull private final Function<ProfileRequestContext, String> principalLookupFunction;
+ /** Strategy used to locate the {@link AttributeContext} associated with a given {@link ProfileRequestContext}. */
+ @Nonnull private Function<ProfileRequestContext,AttributeContext> attributeContextLookupStrategy;
+
/** Manages CAS tickets. */
@Nonnull private final TicketService casTicketService;
@@ -89,7 +95,13 @@ public class GrantServiceTicketAction extends AbstractCASProtocolAction<ServiceT
/** Authentication result. */
@Nullable private AuthenticationResult authnResult;
+
+ /** Whether consent needs to be stored in ticket. */
+ private boolean storeConsent;
+ /** AttributeContext to use. */
+ @Nullable private AttributeContext attributeCtx;
+
/** CAS request. */
@Nullable private ServiceTicketRequest request;
@@ -106,6 +118,25 @@ public class GrantServiceTicketAction extends AbstractCASProtocolAction<ServiceT
authnCtxLookupFunction = new ChildContextLookup<>(AuthenticationContext.class);
principalLookupFunction = new SubjectContextPrincipalLookupFunction().compose(
new ChildContextLookup<>(SubjectContext.class));
+ attributeContextLookupStrategy = new ChildContextLookup<>(AttributeContext.class).compose(
+ new ChildContextLookup<>(RelyingPartyContext.class));
+ }
+
+ /**
+ * Set the strategy used to locate the {@link AttributeContext} associated with a given
+ * {@link ProfileRequestContext}.
+ *
+ * @param strategy strategy used to locate the {@link AttributeContext} associated with a given
+ * {@link ProfileRequestContext}
+ *
+ * @since 4.2.0
+ */
+ public void setAttributeContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, AttributeContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ attributeContextLookupStrategy =
+ Constraint.isNotNull(strategy, "AttributeContext lookup strategy cannot be null");
}
@Override
@@ -155,7 +186,18 @@ public class GrantServiceTicketAction extends AbstractCASProtocolAction<ServiceT
ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
return false;
}
-
+
+ if (loginConfig.getPostAuthenticationFlows(profileRequestContext).contains("attribute-release")) {
+ attributeCtx = attributeContextLookupStrategy.apply(profileRequestContext);
+ if (attributeCtx != null) {
+ storeConsent = attributeCtx.isConsented() || loginConfig.isStoreConsentInTickets(profileRequestContext);
+ if (storeConsent) {
+ log.debug("{} Storing consented attribute IDs into ticket: {}", getLogPrefix(),
+ attributeCtx.getIdPAttributes().keySet());
+ }
+ }
+ }
+
return true;
}
@@ -170,6 +212,11 @@ public class GrantServiceTicketAction extends AbstractCASProtocolAction<ServiceT
getPrincipalName(profileRequestContext),
authnResult.getAuthenticationInstant(),
authnResult.getAuthenticationFlowId());
+
+ if (storeConsent) {
+ state.setConsentedAttributeIds(attributeCtx.getIdPAttributes().keySet());
+ }
+
ticket = casTicketService.createServiceTicket(
securityConfig.getIdGenerator().generateIdentifier(),
Instant.now().plus(loginConfig.getTicketValidityPeriod(profileRequestContext)),
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/PrepareTicketValidationResponseAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/PrepareTicketValidationResponseAction.java
index f14c492c0..d4a2fb5c3 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/PrepareTicketValidationResponseAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/PrepareTicketValidationResponseAction.java
@@ -20,6 +20,7 @@ package net.shibboleth.idp.cas.flow.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -43,6 +44,7 @@ import net.shibboleth.idp.cas.protocol.ProtocolError;
import net.shibboleth.idp.cas.protocol.TicketValidationRequest;
import net.shibboleth.idp.cas.protocol.TicketValidationResponse;
import net.shibboleth.idp.cas.ticket.TicketPrincipalLookupFunction;
+import net.shibboleth.idp.cas.ticket.TicketState;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.utilities.java.support.annotation.constraint.Live;
@@ -94,6 +96,9 @@ public class PrepareTicketValidationResponseAction extends
/** Stored off context from request. */
@Nullable private AttributeContext attributeContext;
+ /** Stored consented attributes from ticket. */
+ @Nullable private Set<String> consentedAttributeIds;
+
/** Profile configuration. */
@Nullable private ValidateConfiguration validateConfiguration;
@@ -157,6 +162,10 @@ public class PrepareTicketValidationResponseAction extends
try {
response = getCASResponse(profileRequestContext);
+ final TicketState state = getCASTicket(profileRequestContext).getTicketState();
+ if (state != null) {
+ consentedAttributeIds = state.getConsentedAttributeIds();
+ }
} catch (final EventException e) {
ActionSupport.buildEvent(profileRequestContext, e.getEventID());
return false;
@@ -213,7 +222,12 @@ public class PrepareTicketValidationResponseAction extends
return;
}
for (final IdPAttribute attribute : inputAttributes) {
- encodeAttribute(component.getComponent(), profileRequestContext, attribute, encodedAttributes);
+ if (consentedAttributeIds == null || consentedAttributeIds.contains(attribute.getId())) {
+ encodeAttribute(component.getComponent(), profileRequestContext, attribute, encodedAttributes);
+ } else {
+ log.info("{} Skipping attribute {} not in stored consent list from ticket", getLogPrefix(),
+ attribute.getId());
+ }
}
} finally {
if (null != component) {
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/ticket/serialization/impl/AbstractTicketSerializer.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/ticket/serialization/impl/AbstractTicketSerializer.java
index 01e606c84..3dbccc5ad 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/ticket/serialization/impl/AbstractTicketSerializer.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/ticket/serialization/impl/AbstractTicketSerializer.java
@@ -21,14 +21,19 @@ import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.time.Instant;
+import java.util.HashSet;
+import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.json.Json;
+import javax.json.JsonArray;
import javax.json.JsonException;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonReaderFactory;
+import javax.json.JsonString;
+import javax.json.JsonValue;
import javax.json.stream.JsonGenerator;
import javax.json.stream.JsonGeneratorFactory;
@@ -52,28 +57,31 @@ import org.slf4j.LoggerFactory;
public abstract class AbstractTicketSerializer<T extends Ticket> implements StorageSerializer<T> {
/** Service field name. */
- private static final String SERVICE_FIELD = "rp";
+ @Nonnull @NotEmpty private static final String SERVICE_FIELD = "rp";
/** Expiration instant field name. */
- private static final String EXPIRATION_FIELD = "exp";
+ @Nonnull @NotEmpty private static final String EXPIRATION_FIELD = "exp";
/** Supplemental ticket state field name. */
- private static final String STATE_FIELD = "ts";
+ @Nonnull @NotEmpty private static final String STATE_FIELD = "ts";
/** Session ID field name. */
- private static final String SESSION_FIELD = "sid";
+ @Nonnull @NotEmpty private static final String SESSION_FIELD = "sid";
/** Authenticated canonical principal name field. */
- private static final String PRINCIPAL_FIELD = "p";
+ @Nonnull @NotEmpty private static final String PRINCIPAL_FIELD = "p";
/** Authentication instant field name. */
- private static final String AUTHN_INSTANT_FIELD = "ai";
+ @Nonnull @NotEmpty private static final String AUTHN_INSTANT_FIELD = "ai";
/** Authentication method field name. */
- private static final String AUTHN_METHOD_FIELD = "am";
+ @Nonnull @NotEmpty private static final String AUTHN_METHOD_FIELD = "am";
+
+ /** Consented attribute IDs field name. */
+ @Nonnull @NotEmpty private static final String CONSENTED_ATTRS_FIELD = "con";
/** Logger instance. */
- private final Logger logger = LoggerFactory.getLogger(AbstractTicketSerializer.class);
+ @Nonnull private final Logger logger = LoggerFactory.getLogger(AbstractTicketSerializer.class);
/** JSON generator factory. */
@Nonnull
@@ -99,13 +107,23 @@ public abstract class AbstractTicketSerializer<T extends Ticket> implements Stor
gen.writeStartObject()
.write(SERVICE_FIELD, ticket.getService())
.write(EXPIRATION_FIELD, ticket.getExpirationInstant().toEpochMilli());
+
if (ticket.getTicketState() != null) {
gen.writeStartObject(STATE_FIELD)
.write(SESSION_FIELD, ticket.getTicketState().getSessionId())
.write(PRINCIPAL_FIELD, ticket.getTicketState().getPrincipalName())
.write(AUTHN_INSTANT_FIELD, ticket.getTicketState().getAuthenticationInstant().toEpochMilli())
- .write(AUTHN_METHOD_FIELD, ticket.getTicketState().getAuthenticationMethod())
- .writeEnd();
+ .write(AUTHN_METHOD_FIELD, ticket.getTicketState().getAuthenticationMethod());
+
+ if (ticket.getTicketState().getConsentedAttributeIds() != null) {
+ gen.writeStartArray(CONSENTED_ATTRS_FIELD);
+ for (final String id : ticket.getTicketState().getConsentedAttributeIds()) {
+ gen.write(id);
+ }
+ gen.writeEnd();
+ }
+
+ gen.writeEnd();
}
serializeInternal(gen, ticket);
gen.writeEnd();
@@ -137,6 +155,16 @@ public abstract class AbstractTicketSerializer<T extends Ticket> implements Stor
so.getString(PRINCIPAL_FIELD),
Instant.ofEpochMilli(so.getJsonNumber(AUTHN_INSTANT_FIELD).longValueExact()),
so.getString(AUTHN_METHOD_FIELD));
+ final JsonValue consent = so.get(CONSENTED_ATTRS_FIELD);
+ if (consent instanceof JsonArray) {
+ final Set<String> idset = new HashSet<>();
+ for (final JsonValue id : (JsonArray) consent) {
+ if (id instanceof JsonString) {
+ idset.add(((JsonString) id).getString());
+ }
+ }
+ state.setConsentedAttributeIds(idset);
+ }
} else {
state = null;
}
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/ticket/serialization/impl/ServiceTicketSerializerTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/ticket/serialization/impl/ServiceTicketSerializerTest.java
index 3692df1d6..21a13590c 100644
--- a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/ticket/serialization/impl/ServiceTicketSerializerTest.java
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/ticket/serialization/impl/ServiceTicketSerializerTest.java
@@ -25,6 +25,8 @@ import static org.testng.Assert.*;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.Set;
/**
* Unit test for {@link ServiceTicketSerializer}.
@@ -66,4 +68,45 @@ public class ServiceTicketSerializerTest {
assertEquals(st2.isRenew(), st1.isRenew());
assertEquals(st2.getTicketState(), st1.getTicketState());
}
+
+ @Test
+ public void testSerializeWithConsent() throws Exception {
+ final ServiceTicket st1 = new ServiceTicket(
+ "ST-0123456789-e6342d467a4414e599aa3c323528e96f",
+ "https://nobody.example.org",
+ Instant.now().truncatedTo(ChronoUnit.MILLIS),
+ true);
+ final TicketState state = new TicketState("idpsess-d2db22058dc178d3b917363859e", "bob",
+ Instant.now().truncatedTo(ChronoUnit.MILLIS), "Password");
+ state.setConsentedAttributeIds(Set.of("foo", "bar"));
+ st1.setTicketState(state);
+ final String serialized = serializer.serialize(st1);
+ final ServiceTicket st2 = serializer.deserialize(1, "notused", st1.getId(), serialized, null);
+ assertEquals(st2.getId(), st1.getId());
+ assertEquals(st2.getService(), st1.getService());
+ assertEquals(st2.getExpirationInstant(), st1.getExpirationInstant());
+ assertEquals(st2.isRenew(), st1.isRenew());
+ assertEquals(st2.getTicketState(), st1.getTicketState());
+ }
+
+ @Test
+ public void testSerializeWithEmptyConsent() throws Exception {
+ final ServiceTicket st1 = new ServiceTicket(
+ "ST-0123456789-e6342d467a4414e599aa3c323528e96f",
+ "https://nobody.example.org",
+ Instant.now().truncatedTo(ChronoUnit.MILLIS),
+ true);
+ final TicketState state = new TicketState("idpsess-d2db22058dc178d3b917363859e", "bob",
+ Instant.now().truncatedTo(ChronoUnit.MILLIS), "Password");
+ state.setConsentedAttributeIds(Collections.emptySet());
+ st1.setTicketState(state);
+ final String serialized = serializer.serialize(st1);
+ final ServiceTicket st2 = serializer.deserialize(1, "notused", st1.getId(), serialized, null);
+ assertEquals(st2.getId(), st1.getId());
+ assertEquals(st2.getService(), st1.getService());
+ assertEquals(st2.getExpirationInstant(), st1.getExpirationInstant());
+ assertEquals(st2.isRenew(), st1.isRenew());
+ assertEquals(st2.getTicketState(), st1.getTicketState());
+ }
+
}
\ No newline at end of file
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/relying-party-mddriven.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/relying-party-mddriven.xml
index 3f195fa9b..ada8951cc 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/relying-party-mddriven.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/relying-party-mddriven.xml
@@ -668,6 +668,14 @@
<constructor-arg value="false" />
</bean>
</property>
+ <property name="storeConsentInTicketsPredicate">
+ <bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
+ <constructor-arg>
+ <bean parent="shibboleth.MDDrivenBoolProperty" p:propertyName="storeConsentInTickets" />
+ </constructor-arg>
+ <constructor-arg value="false" />
+ </bean>
+ </property>
<property name="proxyCountLookupStrategy">
<bean parent="shibboleth.MDDrivenIntProperty" p:propertyName="proxyCount" />
</property>
diff --git a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/SamlValidateFlowTest.java b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/SamlValidateFlowTest.java
index c05ac4a5e..3e274b316 100644
--- a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/SamlValidateFlowTest.java
+++ b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/SamlValidateFlowTest.java
@@ -38,6 +38,7 @@ import javax.annotation.Nonnull;
import static org.testng.Assert.*;
import java.time.Instant;
+import java.util.Set;
/**
* Tests the flow behind the <code>/samlValidate</code> endpoint.
@@ -100,6 +101,39 @@ public class SamlValidateFlowTest extends AbstractFlowTest {
assertPopulatedAttributeContext((ProfileRequestContext) outcome.getOutput().get(END_STATE_OUTPUT_ATTR_NAME));
}
+ @Test
+ public void testSuccessWithConsent() throws Exception {
+ final String principal = "john";
+ final IdPSession session = sessionManager.createSession(principal);
+ final TicketState state = new TicketState(session.getId(), principal, Instant.now(), "Password");
+ state.setConsentedAttributeIds(Set.of("uid", "eduPersonPrincipalName"));
+ final ServiceTicket ticket = ticketService.createServiceTicket(
+ "ST-1415133132-ompog68ygxKyX9BPwPuw0hESQBjuA",
+ Instant.now().plusSeconds(5),
+ "https://test.example.org/",
+ state,
+ false);
+ final String requestBody = SAML_REQUEST_TEMPLATE.replace("@@TICKET@@", ticket.getId());
+ request.setMethod("POST");
+ request.setContentType("text/xml");
+ request.setContent(requestBody.getBytes("UTF-8"));
+ externalContext.getMockRequestParameterMap().put("TARGET", ticket.getService());
+ overrideEndStateOutput(FLOW_ID, "ValidateSuccess");
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+
+ final String responseBody = response.getContentAsString();
+ final FlowExecutionOutcome outcome = result.getOutcome();
+ assertEquals(outcome.getId(), "ValidateSuccess");
+ assertTrue(responseBody.contains("<saml1p:StatusCode Value=\"saml1p:Success\"/>"));
+ assertTrue(responseBody.contains("<saml1:NameIdentifier>john</saml1:NameIdentifier>"));
+ assertTrue(responseBody.contains("<saml1:NameIdentifier>john</saml1:NameIdentifier>"));
+ assertTrue(responseBody.contains("<saml1:Attribute AttributeName=\"uid\" AttributeNamespace=\"http://www.ja-sig.org/products/cas/\"><saml1:AttributeValue xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xsd:string\">john</saml1:AttributeValue></saml1:Attribute>"));
+ assertTrue(responseBody.contains("<saml1:Attribute AttributeName=\"eduPersonPrincipalName\" AttributeNamespace=\"http://www.ja-sig.org/products/cas/\"><saml1:AttributeValue xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xsd:string\">john at example.org</saml1:AttributeValue></saml1:Attribute>"));
+ assertFalse(responseBody.contains("<saml1:Attribute AttributeName=\"mail\" AttributeNamespace=\"http://www.ja-sig.org/products/cas/\"><saml1:AttributeValue xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xsd:string\">john at example.org</saml1:AttributeValue></saml1:Attribute>"));
+ assertPopulatedAttributeContext((ProfileRequestContext) outcome.getOutput().get(END_STATE_OUTPUT_ATTR_NAME));
+ }
+
@Test
public void testFailureTicketExpired() throws Exception {
final String requestBody = SAML_REQUEST_TEMPLATE.replace("@@TICKET@@", "ST-123-abcdefg");
diff --git a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ServiceValidateFlowTest.java b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ServiceValidateFlowTest.java
index a23c3a664..6679f8a57 100644
--- a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ServiceValidateFlowTest.java
+++ b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ServiceValidateFlowTest.java
@@ -18,6 +18,7 @@
package net.shibboleth.idp.test.flows.cas;
import java.time.Instant;
+import java.util.Set;
import javax.annotation.Nonnull;
@@ -102,11 +103,12 @@ public class ServiceValidateFlowTest extends AbstractFlowTest {
public void testSuccess() throws Exception {
final String principal = "john";
final IdPSession session = sessionManager.createSession(principal);
+ final TicketState state = new TicketState(session.getId(), principal, Instant.now(), "Password");
final ServiceTicket ticket = ticketService.createServiceTicket(
"ST-1415133132-ompog68ygxKyX9BPwPuw0hESQBjuA",
Instant.now().plusSeconds(5),
"https://test.example.org/",
- new TicketState(session.getId(), principal, Instant.now(), "Password"),
+ state,
false);
externalContext.getMockRequestParameterMap().put("service", ticket.getService());
@@ -134,6 +136,44 @@ public class ServiceValidateFlowTest extends AbstractFlowTest {
assertEquals(updatedSession.getSPSessions().size(), 0);
}
+ @Test
+ public void testSuccessWithConsent() throws Exception {
+ final String principal = "john";
+ final IdPSession session = sessionManager.createSession(principal);
+ final TicketState state = new TicketState(session.getId(), principal, Instant.now(), "Password");
+ state.setConsentedAttributeIds(Set.of("uid", "eduPersonPrincipalName"));
+ final ServiceTicket ticket = ticketService.createServiceTicket(
+ "ST-1415133132-ompog68ygxKyX9BPwPuw0hESQBjuA",
+ Instant.now().plusSeconds(5),
+ "https://test.example.org/",
+ state,
+ false);
+
+ externalContext.getMockRequestParameterMap().put("service", ticket.getService());
+ externalContext.getMockRequestParameterMap().put("ticket", ticket.getId());
+ overrideEndStateOutput(FLOW_ID, "ValidateSuccess");
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+
+ final String responseBody = response.getContentAsString();
+ final FlowExecutionOutcome outcome = result.getOutcome();
+ assertEquals(outcome.getId(), "ValidateSuccess");
+ assertTrue(responseBody.contains("<cas:authenticationSuccess>"));
+ assertTrue(responseBody.contains("<cas:user>john</cas:user>"));
+ assertTrue(responseBody.contains("<cas:attributes>"));
+ assertTrue(responseBody.contains("<cas:uid>john</cas:uid>"));
+ assertTrue(responseBody.contains("<cas:eduPersonPrincipalName>john at example.org</cas:eduPersonPrincipalName>"));
+ assertFalse(responseBody.contains("<cas:mail>john at example.org</cas:mail>"));
+ assertFalse(responseBody.contains("<cas:proxyGrantingTicket>"));
+ assertFalse(responseBody.contains("<cas:proxies>"));
+ assertPopulatedAttributeContext((ProfileRequestContext) outcome.getOutput().get(END_STATE_OUTPUT_ATTR_NAME));
+
+ final IdPSession updatedSession = sessionResolver.resolveSingle(
+ new CriteriaSet(new SessionIdCriterion(session.getId())));
+ assertNotNull(updatedSession);
+ assertEquals(updatedSession.getSPSessions().size(), 0);
+ }
+
@Test
public void testSuccessWithSLOParticipant() throws Exception {
final String principal = "john";
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list