[java-identity-provider] branch main updated: IDP-2353 - C14n flow that runs a deployer-defined bean
Scott Cantor
cantor.2 at osu.edu
Thu Feb 27 19:14:48 UTC 2025
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=7fe2c97c1c721424c7492dd6805ff6bfe2f4a4aa
The following commit(s) were added to refs/heads/main by this push:
new 7fe2c97c1 IDP-2353 - C14n flow that runs a deployer-defined bean
7fe2c97c1 is described below
commit 7fe2c97c1c721424c7492dd6805ff6bfe2f4a4aa
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Feb 27 14:14:29 2025 -0500
IDP-2353 - C14n flow that runs a deployer-defined bean
https://shibboleth.atlassian.net/browse/IDP-2353
Reimplement attribute-sourced c14n inline, leaving orignal in place.
---
.../AttributeSourcedSubjectCanonicalization.java | 269 ++++++++++++++++-----
...ibuteSourcedSubjectCanonicalizationAction.java} | 9 +-
...eSourcedSubjectCanonicalizationActionTest.java} | 12 +-
...ttributeSourcedSubjectCanonicalizationTest.java | 136 +++++++----
.../shibboleth/idp/conf/subject-c14n-system.xml | 44 +++-
.../c14n/attribute-sourced-subject-c14n-beans.xml | 2 +-
6 files changed, 340 insertions(+), 132 deletions(-)
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java
index 54ef3d64d..92e307594 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java
@@ -15,18 +15,21 @@
package net.shibboleth.idp.authn.impl;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.security.auth.Subject;
import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.messaging.context.navigate.RecursiveTypedParentContextLookup;
+import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
@@ -35,70 +38,114 @@ import net.shibboleth.idp.attribute.IdPAttributeValue;
import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
import net.shibboleth.idp.attribute.StringAttributeValue;
import net.shibboleth.idp.attribute.context.AttributeContext;
-import net.shibboleth.idp.authn.AbstractSubjectCanonicalizationAction;
+import net.shibboleth.idp.attribute.resolver.AttributeResolver;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.authn.AbstractSubjectCanonicalizer;
import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.SubjectCanonicalizationException;
+import net.shibboleth.idp.authn.SubjectCanonicalizer;
import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
+import net.shibboleth.idp.authn.context.SubjectContext;
+import net.shibboleth.idp.authn.context.navigate.SubjectContextPrincipalLookupFunction;
import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
+import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
+import net.shibboleth.profile.context.navigate.RelyingPartyIdLookupFunction;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.logic.PredicateSupport;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.service.ReloadableService;
+import net.shibboleth.shared.service.ServiceException;
+import net.shibboleth.shared.service.ServiceableComponent;
/**
- * An action that extracts a resolved {@link IdPAttribute} value from an {@link AttributeContext} child obtained via
- * lookup function (by default a child of the {@link SubjectCanonicalizationContext}), and uses it as the result
- * of subject canonicalization.
- *
- * <p>This action operates on a set of previously resolved attributes that are presumed to have been generated based
- * in some fashion on the content of the {@link SubjectCanonicalizationContext}.</p>
+ * A {@link SubjectCanonicalizer} that resolves attributes and then extracts a resolved {@link IdPAttribute} value
+ * from an {@link AttributeContext} child obtained via lookup function (by default a child of the
+ * {@link SubjectCanonicalizationContext}), and uses it as the result of subject canonicalization.
*
* <p>String and scoped attribute values are supported.</p>
*
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#INVALID_SUBJECT}
- * @pre <pre>ProfileRequestContext.getSubcontext(SubjectCanonicalizationContext.class) != null</pre>
- * @post <pre>SubjectCanonicalizationContext.getPrincipalName() != null
- * || SubjectCanonicalizationContext.getException() != null</pre>
+ * @since 5.2.0
*/
-public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCanonicalizationAction {
+public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCanonicalizer {
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(AttributeSourcedSubjectCanonicalization.class);
+ /** Attribute resolver service. */
+ @NonnullAfterInit private ReloadableService<AttributeResolver> resolverService;
+
+ /** Condition controlling whether to run the attribute resolver. */
+ @Nonnull private Predicate<ProfileRequestContext> resolutionCondition;
+
/** Delimiter to use for scoped attribute serialization. */
private char delimiter;
- /** Whether to also check the original Subject for {@link IdPAttributePrincipal}s. */
+ /** Whether to also (or instead) check the original Subject for {@link IdPAttributePrincipal}s. */
private boolean resolveFromSubject;
- /** Indexed attributes pulled from subject. */
- @Nonnull private Map<String,IdPAttribute> subjectSourcedAttributes;
+ /** Attributes to resolve if explicit. */
+ @Nonnull private Collection<String> attributesToResolve;
/** Ordered list of attributes to look for and read from. */
@Nonnull private List<String> attributeSourceIds;
- /** Lookup strategy for {@link AttributeContext} to read from. */
- @Nonnull private Function<ProfileRequestContext,AttributeContext> attributeContextLookupStrategy;
+ /** Strategy used to locate the identity of the issuer associated with the attribute resolution. */
+ @Nullable private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Strategy used to locate the identity of the recipient associated with the attribute resolution. */
+ @Nullable private Function<ProfileRequestContext,String> recipientLookupStrategy;
- /** The context to read from. */
- @Nullable private AttributeContext attributeCtx;
+ /** Strategy used to locate the principal name associated with the attribute resolution. */
+ @Nullable private Function<ProfileRequestContext,String> principalNameLookupStrategy;
/** Constructor. */
public AttributeSourcedSubjectCanonicalization() {
delimiter = '@';
+ attributesToResolve = CollectionSupport.emptyList();
attributeSourceIds = CollectionSupport.emptyList();
- subjectSourcedAttributes = CollectionSupport.emptyMap();
- attributeContextLookupStrategy =
- new ChildContextLookup<>(AttributeContext.class).compose(
- new ChildContextLookup<>(SubjectCanonicalizationContext.class));
+ resolutionCondition = PredicateSupport.alwaysTrue();
+
+ issuerLookupStrategy = new IssuerLookupFunction();
+ recipientLookupStrategy = new RelyingPartyIdLookupFunction();
+
+ principalNameLookupStrategy =
+ new SubjectContextPrincipalLookupFunction().compose(
+ new ChildContextLookup<>(SubjectContext.class));
+ }
+
+ /**
+ * Set the {@link AttributeResolver} service instance to use.
+ *
+ * @param service service to use
+ */
+ public void setAttributeResolver(@Nonnull final ReloadableService<AttributeResolver> service) {
+ checkSetterPreconditions();
+ resolverService = Constraint.isNotNull(service, "AttributeResolver service cannot be null");
+ }
+
+ /**
+ * Set a condition to evaluate to decide whether to resolve attributes.
+ *
+ * <p>Defaults to true.</p>
+ *
+ * @param condition condition to set
+ */
+ public void setResolutionCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ checkSetterPreconditions();
+ resolutionCondition = Constraint.isNotNull(condition, "Attribute resolution condition cannot be null");
}
/**
* Set the delimiter to use for serializing scoped attribute values.
*
+ * <p>Defaults to '@'.</p>
+ *
* @param ch delimiter to use
*/
public void setScopedDelimiter(final char ch) {
@@ -110,15 +157,25 @@ public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCano
* Whether to include any {@link IdPAttributePrincipal} objects found in the input {@link Subject}
* when searching for a matching attribute ID.
*
- * @param flag flag to set
+ * <p>Defaults to false.</p>
*
- * @since 4.1.0
+ * @param flag flag to set
*/
public void setResolveFromSubject(final boolean flag) {
checkSetterPreconditions();
resolveFromSubject = flag;
}
+ /**
+ * Set the attribute IDs to resolve.
+ *
+ * @param ids attribute IDs to resolve
+ */
+ public void setAttributesToResolve(@Nonnull final Collection<String> ids) {
+ checkSetterPreconditions();
+ attributesToResolve = StringSupport.normalizeStringCollection(ids);
+ }
+
/**
* Set the attribute IDs to read from in order of preference.
*
@@ -130,15 +187,33 @@ public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCano
}
/**
- * Set the lookup strategy for the {@link AttributeContext} to read from.
+ * Set the strategy used to lookup the issuer for this attribute resolution.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
+ checkSetterPreconditions();
+ issuerLookupStrategy = strategy;
+ }
+
+ /**
+ * Set the strategy used to lookup the recipient for this attribute resolution.
*
* @param strategy lookup strategy
*/
- public void setAttributeContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext,AttributeContext> strategy) {
+ public void setRecipientLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
+ checkSetterPreconditions();
+ recipientLookupStrategy = strategy;
+ }
+
+ /**
+ * Set the strategy used to locate the principal name for this attribute resolution.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setPrincipalNameLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
checkSetterPreconditions();
- attributeContextLookupStrategy = Constraint.isNotNull(strategy,
- "AttributeContext lookup strategy cannot be null");
+ principalNameLookupStrategy = strategy;
}
/** {@inheritDoc} */
@@ -148,44 +223,38 @@ public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCano
if (attributeSourceIds.isEmpty()) {
throw new ComponentInitializationException("Attribute source ID list cannot be empty");
+ } else if (resolverService == null) {
+ throw new ComponentInitializationException("AttributeResolver service cannot be null");
}
}
/** {@inheritDoc} */
@Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final SubjectCanonicalizationContext c14nContext) {
+ @Nullable protected String doApply(@Nonnull final SubjectCanonicalizationContext c14nContext) {
- if (!super.doPreExecute(profileRequestContext, c14nContext)) {
- return false;
+ // Find first PRC walking up from c14n context.
+ final ProfileRequestContext profileRequestContext =
+ new RecursiveTypedParentContextLookup<>(ProfileRequestContext.class).apply(c14nContext);
+ if (profileRequestContext == null) {
+ log.error("{} Unable to locate ProfileRequestContext", getLogPrefix());
+ return AuthnEventIds.INVALID_SUBJECT_C14N_CTX;
}
-
+
+ final Map<String,IdPAttribute> subjectSourcedAttributes;
if (resolveFromSubject) {
- final Subject subject =
- Constraint.isNotNull(c14nContext.getSubject(), "Expected a non-null Subject");
- final Set<IdPAttributePrincipal> subjectSourced = subject.getPrincipals(IdPAttributePrincipal.class);
- if (subjectSourced != null && !subjectSourced.isEmpty()) {
- subjectSourcedAttributes = new HashMap<>(subjectSourced.size());
- subjectSourced.forEach(a -> subjectSourcedAttributes.put(a.getAttribute().getId(), a.getAttribute()));
- }
+ subjectSourcedAttributes = extractSubjectSourcedAttributes(c14nContext);
+ } else {
+ subjectSourcedAttributes = CollectionSupport.emptyMap();
}
- final AttributeContext aCtx = attributeCtx = attributeContextLookupStrategy.apply(profileRequestContext);
- if (subjectSourcedAttributes.isEmpty() && (aCtx == null || aCtx.getIdPAttributes().isEmpty())) {
- log.warn("{} No attributes found, canonicalization not possible", getLogPrefix());
- c14nContext.setException(new SubjectCanonicalizationException("No attributes were found"));
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_SUBJECT);
- return false;
+ final Map<String,IdPAttribute> resolvedAttributes;
+ if (resolutionCondition.test(profileRequestContext)) {
+ resolvedAttributes = resolveAttributes(profileRequestContext, c14nContext);
+ } else {
+ log.debug("{} Skipping attribute resolution based on configured condition", getLogPrefix());
+ resolvedAttributes = CollectionSupport.emptyMap();
}
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final SubjectCanonicalizationContext c14nContext) {
-
for (final String id : attributeSourceIds) {
IdPAttribute attr = subjectSourcedAttributes.get(id);
@@ -193,25 +262,95 @@ public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCano
final String result = findValue(attr);
if (result != null) {
c14nContext.setPrincipalName(result);
- return;
+ return EventIds.PROCEED_EVENT_ID;
}
- } else if (attributeCtx != null) {
- attr = attributeCtx.getIdPAttributes().get(id);
+ } else {
+ attr = resolvedAttributes.get(id);
if (attr != null) {
final String result = findValue(attr);
if (result != null) {
c14nContext.setPrincipalName(result);
- return;
+ return EventIds.PROCEED_EVENT_ID;
}
}
}
}
log.info("{} Attribute sources {} did not produce a usable identifier", getLogPrefix(), attributeSourceIds);
- c14nContext.setException(new SubjectCanonicalizationException("No usable attribute values were found"));
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_SUBJECT);
+ return AuthnEventIds.INVALID_SUBJECT;
+ }
+
+ /**
+ * Extract and populate a map of any attributes found in the Subject.
+ *
+ * @param c14nContext source of Subject
+ *
+ * @return possibly empty map
+ */
+ @Nonnull private Map<String,IdPAttribute> extractSubjectSourcedAttributes(
+ @Nonnull final SubjectCanonicalizationContext c14nContext) {
+
+ final Subject subject = Constraint.isNotNull(c14nContext.getSubject(), "Expected a non-null Subject");
+
+ final Set<IdPAttributePrincipal> subjectSourced = subject.getPrincipals(IdPAttributePrincipal.class);
+ if (subjectSourced != null && !subjectSourced.isEmpty()) {
+ final Map<String,IdPAttribute> subjectSourcedAttributes = new HashMap<>(subjectSourced.size());
+ subjectSourced.forEach(a -> subjectSourcedAttributes.put(a.getAttribute().getId(), a.getAttribute()));
+ return subjectSourcedAttributes;
+ }
+
+ return CollectionSupport.emptyMap();
}
+
+ /**
+ * Perform attribute resolution inline.
+ *
+ * @param profileRequestContext profile request context
+ * @param c14nContext subject c14n context
+ *
+ * @return the resolved map of attributes
+ */
+ private Map<String,IdPAttribute> resolveAttributes(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final SubjectCanonicalizationContext c14nContext) {
+
+ final AttributeResolutionContext resolutionContext =
+ c14nContext.ensureSubcontext(AttributeResolutionContext.class);
+
+ resolutionContext.setProfileRequestContextLookupStrategy(FunctionSupport.constant(profileRequestContext));
+
+ final var flow = c14nContext.getAttemptedFlow();
+ if (flow != null) {
+ resolutionContext.setResolutionLabel(flow.ensureId());
+ }
+ resolutionContext.setRequestedIdPAttributeNames(attributesToResolve);
+ if (null != principalNameLookupStrategy) {
+ resolutionContext.setPrincipal(principalNameLookupStrategy.apply(profileRequestContext));
+ }
+ if (recipientLookupStrategy != null) {
+ resolutionContext.setAttributeRecipientID(recipientLookupStrategy.apply(profileRequestContext));
+ }
+ if (issuerLookupStrategy != null) {
+ resolutionContext.setAttributeIssuerID(issuerLookupStrategy.apply(profileRequestContext));
+ }
+
+ try (final ServiceableComponent<AttributeResolver> component =
+ resolverService.getServiceableComponent()) {
+ final AttributeResolver attributeResolver = component.getComponent();
+ attributeResolver.resolveAttributes(resolutionContext);
+ c14nContext.removeSubcontext(resolutionContext);
+ return resolutionContext.getResolvedIdPAttributes();
+ } catch (final ResolutionException e) {
+ c14nContext.removeSubcontext(resolutionContext);
+ log.error("{} Error resolving attributes", getLogPrefix(), e);
+ return CollectionSupport.emptyMap();
+ } catch (final ServiceException e) {
+ c14nContext.removeSubcontext(resolutionContext);
+ log.error("{} Invalid AttributeResolver configuration", getLogPrefix(), e);
+ return CollectionSupport.emptyMap();
+ }
+ }
+
/**
* Check for a compatible value in the input attribute.
*
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationAction.java
similarity index 97%
copy from idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java
copy to idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationAction.java
index 54ef3d64d..2edd580e4 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationAction.java
@@ -56,16 +56,19 @@ import net.shibboleth.shared.primitive.StringSupport;
*
* <p>String and scoped attribute values are supported.</p>
*
+ * @deprecated
+ *
* @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
* @event {@link AuthnEventIds#INVALID_SUBJECT}
* @pre <pre>ProfileRequestContext.getSubcontext(SubjectCanonicalizationContext.class) != null</pre>
* @post <pre>SubjectCanonicalizationContext.getPrincipalName() != null
* || SubjectCanonicalizationContext.getException() != null</pre>
*/
-public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCanonicalizationAction {
+ at Deprecated(since="5.2.0", forRemoval=true)
+public class AttributeSourcedSubjectCanonicalizationAction extends AbstractSubjectCanonicalizationAction {
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(AttributeSourcedSubjectCanonicalization.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AttributeSourcedSubjectCanonicalizationAction.class);
/** Delimiter to use for scoped attribute serialization. */
private char delimiter;
@@ -86,7 +89,7 @@ public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCano
@Nullable private AttributeContext attributeCtx;
/** Constructor. */
- public AttributeSourcedSubjectCanonicalization() {
+ public AttributeSourcedSubjectCanonicalizationAction() {
delimiter = '@';
attributeSourceIds = CollectionSupport.emptyList();
subjectSourcedAttributes = CollectionSupport.emptyMap();
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationActionTest.java
similarity index 95%
copy from idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java
copy to idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationActionTest.java
index 4a1dce700..d9f671cbf 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationActionTest.java
@@ -33,23 +33,23 @@ import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.component.ComponentInitializationException;
-/** {@link AttributeSourcedSubjectCanonicalization} unit test. */
- at SuppressWarnings("javadoc")
-public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticationContextTest {
+/** {@link AttributeSourcedSubjectCanonicalizationAction} unit test. */
+ at SuppressWarnings({"javadoc", "removal"})
+public class AttributeSourcedSubjectCanonicalizationActionTest extends BaseAuthenticationContextTest {
- private AttributeSourcedSubjectCanonicalization action;
+ private AttributeSourcedSubjectCanonicalizationAction action;
@BeforeMethod
public void setUp() throws ComponentInitializationException {
super.setUp();
- action = new AttributeSourcedSubjectCanonicalization();
+ action = new AttributeSourcedSubjectCanonicalizationAction();
action.setAttributeSourceIds(CollectionSupport.listOf("attr1", "attr2"));
}
@Test(expectedExceptions=ComponentInitializationException.class)
public void testNoSources() throws ComponentInitializationException {
- action = new AttributeSourcedSubjectCanonicalization();
+ action = new AttributeSourcedSubjectCanonicalizationAction();
action.initialize();
}
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java
index 4a1dce700..22f8b6708 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java
@@ -14,9 +14,14 @@
package net.shibboleth.idp.authn.impl;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import javax.security.auth.Subject;
-import org.springframework.webflow.execution.Event;
+import org.opensaml.profile.action.EventIds;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -25,53 +30,74 @@ import net.shibboleth.idp.attribute.IdPAttribute;
import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
import net.shibboleth.idp.attribute.StringAttributeValue;
import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.idp.attribute.resolver.AttributeResolver;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.SubjectCanonicalizationFlowDescriptor;
import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
import net.shibboleth.idp.authn.impl.testing.BaseAuthenticationContextTest;
import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
-import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.testing.MockReloadableService;
/** {@link AttributeSourcedSubjectCanonicalization} unit test. */
@SuppressWarnings("javadoc")
public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticationContextTest {
+ private SubjectCanonicalizationFlowDescriptor flow;
private AttributeSourcedSubjectCanonicalization action;
+ private Collection<IdPAttribute> mockData;
+
@BeforeMethod
public void setUp() throws ComponentInitializationException {
super.setUp();
+ flow = new SubjectCanonicalizationFlowDescriptor();
+ flow.setId("c14n/test");
+ flow.initialize();
+
+ mockData = new ArrayList<>();
+
action = new AttributeSourcedSubjectCanonicalization();
+ action.setId("test");
action.setAttributeSourceIds(CollectionSupport.listOf("attr1", "attr2"));
+ final MockReloadableService<AttributeResolver> mockResolver =
+ new MockReloadableService<AttributeResolver>(new MockAttributeResolver());
+ action.setAttributeResolver(mockResolver);
+
+ prc.ensureSubcontext(SubjectCanonicalizationContext.class).setAttemptedFlow(flow);
}
@Test(expectedExceptions=ComponentInitializationException.class)
public void testNoSources() throws ComponentInitializationException {
action = new AttributeSourcedSubjectCanonicalization();
+ action.setId("test");
action.initialize();
}
@Test public void testNoContext() throws ComponentInitializationException {
action.initialize();
- final Event event = action.execute(src);
+ prc.removeSubcontext(SubjectCanonicalizationContext.class);
- ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_SUBJECT_C14N_CTX);
+ final String event = action.apply(null);
+
+ Assert.assertEquals(event, AuthnEventIds.INVALID_SUBJECT_C14N_CTX);
}
@Test public void testNoAttributes() throws ComponentInitializationException {
action.initialize();
- Subject subject = new Subject();
- prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
+ final Subject subject = new Subject();
+ final var scc = prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
- final Event event = action.execute(src);
+ final String event = action.apply(scc);
- ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_SUBJECT);
- SubjectCanonicalizationContext scc = prc.getSubcontext(SubjectCanonicalizationContext.class);
- assert scc != null && scc.getException()!=null;
+ Assert.assertEquals(event, AuthnEventIds.INVALID_SUBJECT);
+ Assert.assertNull(scc.getPrincipalName());
}
@Test public void testNoSubjectSourcedAttributes() throws ComponentInitializationException {
@@ -79,13 +105,12 @@ public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticat
action.initialize();
Subject subject = new Subject();
- prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
+ final var scc = prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
- final Event event = action.execute(src);
+ final String event = action.apply(scc);
- ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_SUBJECT);
- SubjectCanonicalizationContext scc = prc.getSubcontext(SubjectCanonicalizationContext.class);
- assert scc != null && scc.getException()!=null;
+ Assert.assertEquals(event, AuthnEventIds.INVALID_SUBJECT);
+ Assert.assertNull(scc.getPrincipalName());
}
@Test public void testSuccess() throws ComponentInitializationException {
@@ -94,15 +119,15 @@ public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticat
final IdPAttribute inputAttribute = new IdPAttribute("attr2");
inputAttribute.setValues(CollectionSupport.singletonList(new StringAttributeValue("foo")));
- final SubjectCanonicalizationContext sc = prc.ensureSubcontext(SubjectCanonicalizationContext.class);
- sc.setSubject(new Subject());
+ final SubjectCanonicalizationContext scc = prc.ensureSubcontext(SubjectCanonicalizationContext.class);
+ scc.setSubject(new Subject());
- sc.ensureSubcontext(AttributeContext.class).setIdPAttributes(CollectionSupport.singleton(inputAttribute));
+ mockData.add(inputAttribute);
- final Event event = action.execute(src);
+ final String event = action.apply(scc);
- ActionTestingSupport.assertProceedEvent(event);
- Assert.assertEquals(sc.getPrincipalName(), "foo");
+ Assert.assertEquals(event, EventIds.PROCEED_EVENT_ID);
+ Assert.assertEquals(scc.getPrincipalName(), "foo");
}
@Test public void testSubjectSourcedSuccess() throws ComponentInitializationException {
@@ -111,15 +136,15 @@ public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticat
final IdPAttribute inputAttribute = new IdPAttribute("attr2");
inputAttribute.setValues(CollectionSupport.singletonList(new StringAttributeValue("foo")));
- final SubjectCanonicalizationContext sc = prc.ensureSubcontext(SubjectCanonicalizationContext.class);
final Subject subject = new Subject();
- sc.setSubject(subject);
subject.getPrincipals().add(new IdPAttributePrincipal(inputAttribute));
+ final SubjectCanonicalizationContext scc = prc.ensureSubcontext(SubjectCanonicalizationContext.class);
+ scc.setSubject(subject);
- final Event event = action.execute(src);
+ final String event = action.apply(scc);
- ActionTestingSupport.assertProceedEvent(event);
- Assert.assertEquals(sc.getPrincipalName(), "foo");
+ Assert.assertEquals(event, EventIds.PROCEED_EVENT_ID);
+ Assert.assertEquals(scc.getPrincipalName(), "foo");
}
@Test public void testSubjectSourcedScopedSuccess() throws ComponentInitializationException {
@@ -128,15 +153,15 @@ public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticat
final IdPAttribute inputAttribute = new IdPAttribute("attr2");
inputAttribute.setValues(CollectionSupport.singletonList(new ScopedStringAttributeValue("foo", "scope")));
- final SubjectCanonicalizationContext sc = prc.ensureSubcontext(SubjectCanonicalizationContext.class);
final Subject subject = new Subject();
- sc.setSubject(subject);
subject.getPrincipals().add(new IdPAttributePrincipal(inputAttribute));
+ final SubjectCanonicalizationContext scc = prc.ensureSubcontext(SubjectCanonicalizationContext.class);
+ scc.setSubject(subject);
- final Event event = action.execute(src);
+ final String event = action.apply(scc);
- ActionTestingSupport.assertProceedEvent(event);
- Assert.assertEquals(sc.getPrincipalName(), "foo at scope");
+ Assert.assertEquals(event, EventIds.PROCEED_EVENT_ID);
+ Assert.assertEquals(scc.getPrincipalName(), "foo at scope");
}
@Test public void testDualSubjectSourcedSuccess() throws ComponentInitializationException {
@@ -149,26 +174,26 @@ public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticat
final IdPAttribute attr2bar = new IdPAttribute("attr2");
attr2bar.setValues(CollectionSupport.singletonList(new StringAttributeValue("bar")));
- final SubjectCanonicalizationContext sc = prc.ensureSubcontext(SubjectCanonicalizationContext.class);
final Subject subject = new Subject();
- sc.setSubject(subject);
subject.getPrincipals().add(new IdPAttributePrincipal(attr2));
+ final SubjectCanonicalizationContext scc = prc.ensureSubcontext(SubjectCanonicalizationContext.class);
+ scc.setSubject(subject);
- sc.ensureSubcontext(AttributeContext.class).setIdPAttributes(CollectionSupport.singleton(attr2bar));
+ mockData.add(attr2bar);
- Event event = action.execute(src);
+ String event = action.apply(scc);
- ActionTestingSupport.assertProceedEvent(event);
- Assert.assertEquals(sc.getPrincipalName(), "foo");
+ Assert.assertEquals(event, EventIds.PROCEED_EVENT_ID);
+ Assert.assertEquals(scc.getPrincipalName(), "foo");
subject.getPrincipals().clear();
subject.getPrincipals().add(new IdPAttributePrincipal(attr2bar));
- sc.ensureSubcontext(AttributeContext.class).setIdPAttributes(CollectionSupport.singleton(attr2));
+ scc.ensureSubcontext(AttributeContext.class).setIdPAttributes(CollectionSupport.singleton(attr2));
- event = action.execute(src);
+ event = action.apply(scc);
- ActionTestingSupport.assertProceedEvent(event);
- Assert.assertEquals(sc.getPrincipalName(), "bar");
+ Assert.assertEquals(event, EventIds.PROCEED_EVENT_ID);
+ Assert.assertEquals(scc.getPrincipalName(), "bar");
}
@Test public void testDualSubjectSourcedSuccess2() throws ComponentInitializationException {
@@ -181,17 +206,34 @@ public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticat
final IdPAttribute attr1 = new IdPAttribute("attr1");
attr1.setValues(CollectionSupport.singletonList(new StringAttributeValue("foo")));
- final SubjectCanonicalizationContext sc = prc.ensureSubcontext(SubjectCanonicalizationContext.class);
final Subject subject = new Subject();
- sc.setSubject(subject);
subject.getPrincipals().add(new IdPAttributePrincipal(attr2));
+ final SubjectCanonicalizationContext scc = prc.ensureSubcontext(SubjectCanonicalizationContext.class);
+ scc.setSubject(subject);
- sc.ensureSubcontext(AttributeContext.class).setIdPAttributes(CollectionSupport.singleton(attr1));
+ mockData.add(attr1);
- final Event event = action.execute(src);
+ final String event = action.apply(scc);
- ActionTestingSupport.assertProceedEvent(event);
- Assert.assertEquals(sc.getPrincipalName(), "foo");
+ Assert.assertEquals(event, EventIds.PROCEED_EVENT_ID);
+ Assert.assertEquals(scc.getPrincipalName(), "foo");
+ }
+
+ public class MockAttributeResolver implements AttributeResolver {
+
+ /** {@inheritDoc} */
+ @Nullable public String getId() {
+ return "mockResolver";
+ }
+
+ /** {@inheritDoc} */
+ public void resolveAttributes(@Nonnull final AttributeResolutionContext resolutionContext)
+ throws ResolutionException {
+
+ Assert.assertEquals(resolutionContext.getResolutionLabel(), "c14n/test");
+
+ resolutionContext.setResolvedIdPAttributes(mockData);
+ }
}
}
\ No newline at end of file
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/subject-c14n-system.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/subject-c14n-system.xml
index 3266d9b96..c20c77f3f 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/subject-c14n-system.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/subject-c14n-system.xml
@@ -29,10 +29,15 @@
c:_0="#{'%{idp.c14n.x500.subjectAltNameTypes:}'.trim()}" />
<bean id="shibboleth.c14n.x500.ObjectIDs" parent="shibboleth.CommaDelimStringArray"
c:_0="#{'%{idp.c14n.x500.objectIDs:}'.trim()}" />
+ <bean id="shibboleth.c14n.attribute.AttributesToResolve" parent="shibboleth.CommaDelimStringArray"
+ c:_0="#{'%{idp.c14n.attribute.attributesToResolve:}'.trim()}" />
+ <bean id="shibboleth.c14n.attribute.AttributeSourceIds" parent="shibboleth.CommaDelimStringArray"
+ c:_0="#{'%{idp.c14n.attribute.attributeSourceIds:%{idp.c14n.attribute.attributesToResolve:}}'.trim()}" />
<!-- Legacy compatibility. -->
<import resource="conditional:%{idp.home}/conf/c14n/simple-subject-c14n-config.xml" />
<import resource="conditional:%{idp.home}/conf/c14n/x500-subject-c14n-config.xml" />
+ <import resource="conditional:%{idp.home}/conf/c14n/attribute-sourced-subject-c14n-config.xml" />
<!-- Parent beans for defining flows of the three typical types. -->
@@ -49,7 +54,7 @@
<bean id="c14n/simple" parent="shibboleth.PostLoginSubjectCanonicalizationFlow">
<property name="implementation">
- <bean id="SimpleSubjectCanonicalization" class="net.shibboleth.idp.authn.impl.SimpleSubjectCanonicalization"
+ <bean id="SimpleC14N" class="net.shibboleth.idp.authn.impl.SimpleSubjectCanonicalization"
p:lowercase="#{getObject('shibboleth.c14n.simple.Lowercase') ?: %{idp.c14n.simple.lowercase:false}}"
p:uppercase="#{getObject('shibboleth.c14n.simple.Uppercase') ?: %{idp.c14n.simple.uppercase:false}}"
p:trim="#{getObject('shibboleth.c14n.simple.Trim') ?: %{idp.c14n.simple.trim:true}}"
@@ -59,7 +64,7 @@
<bean id="c14n/x500" parent="shibboleth.PostLoginSubjectCanonicalizationFlow">
<property name="implementation">
- <bean id="X500SubjectCanonicalization" class="net.shibboleth.idp.authn.impl.X500SubjectCanonicalization"
+ <bean id="X500C14N" class="net.shibboleth.idp.authn.impl.X500SubjectCanonicalization"
p:subjectAltNameTypes-ref="shibboleth.c14n.x500.SubjectAltNameTypes"
p:objectIds-ref="shibboleth.c14n.x500.ObjectIDs"
p:lowercase="#{getObject('shibboleth.c14n.x500.Lowercase') ?: %{idp.c14n.x500.lowercase:false}}"
@@ -69,12 +74,31 @@
</property>
</bean>
+ <bean id="c14n/attribute-sourced" parent="shibboleth.PostLoginSubjectCanonicalizationFlow">
+ <property name="implementation">
+ <bean id="AttributeSourcedC14N" parent="shibboleth.AttributeSourcedC14N" />
+ </property>
+ </bean>
+
+ <bean id="shibboleth.AttributeSourcedC14N"
+ class="net.shibboleth.idp.authn.impl.AttributeSourcedSubjectCanonicalization"
+ p:attributeResolver-ref="shibboleth.AttributeResolverService"
+ p:attributesToResolve-ref="shibboleth.c14n.attribute.AttributesToResolve"
+ p:attributeSourceIds-ref="shibboleth.c14n.attribute.AttributeSourceIds"
+ p:resolveFromSubject="%{idp.c14n.attribute.resolveFromSubject:false}"
+ p:resolutionCondition-ref="#{'%{idp.c14n.attribute.resolutionCondition:shibboleth.Conditions.TRUE}'.trim()}"
+ p:principalNameLookupStrategy="#{getObject('shibboleth.c14n.attribute.PrincipalNameLookupStrategy')}"
+ p:lowercase="%{idp.c14n.attribute.lowercase:false}"
+ p:uppercase="%{idp.c14n.attribute.uppercase:false}"
+ p:trim="%{idp.c14n.attribute.trim:true}"
+ p:transforms="#{getObject('shibboleth.c14n.attribute.Transforms')}" />
+
<!-- The rest are machinery for handling NameIDs, mostly in protocol requests, also for proxying. -->
<bean id="c14n/SAML2Persistent" parent="shibboleth.AbstractSAML2C14NFlowBean"
p:formats="#{ {'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'} }">
<property name="implementation">
- <bean id="NameIDCanonicalization/SAML2Persistent" class="net.shibboleth.idp.saml.nameid.impl.NameIDCanonicalization">
+ <bean id="NameIDC14N/SAML2Persistent" class="net.shibboleth.idp.saml.nameid.impl.NameIDCanonicalization">
<property name="decoder">
<bean class="net.shibboleth.idp.saml.nameid.impl.StoredPersistentIdDecoder"
p:persistentIdStore="#{getObject('%{idp.persistentId.store:undefined}'.trim())}"
@@ -87,7 +111,7 @@
<bean id="c14n/SAML2Transient" parent="shibboleth.AbstractSAML2C14NFlowBean"
p:formats="#{ {'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'} }">
<property name="implementation">
- <bean id="NameIDCanonicalization/SAML2Transient" class="net.shibboleth.idp.saml.nameid.impl.NameIDCanonicalization">
+ <bean id="NameIDC14N/SAML2Transient" class="net.shibboleth.idp.saml.nameid.impl.NameIDCanonicalization">
<property name="decoder">
<bean class="net.shibboleth.idp.saml.nameid.impl.TransientNameIDDecoder"
p:idStore-ref="shibboleth.StorageService" />
@@ -99,7 +123,7 @@
<bean id="c14n/SAML2CryptoTransient" parent="shibboleth.AbstractSAML2C14NFlowBean"
p:formats="#{ {'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'} }">
<property name="implementation">
- <bean id="NameIDCanonicalization/SAML2CryptoTransient" class="net.shibboleth.idp.saml.nameid.impl.NameIDCanonicalization">
+ <bean id="NameIDC14N/SAML2CryptoTransient" class="net.shibboleth.idp.saml.nameid.impl.NameIDCanonicalization">
<property name="decoder">
<bean class="net.shibboleth.idp.saml.nameid.impl.CryptoTransientNameIDDecoder"
p:dataSealer="#{(environment.containsProperty('idp.sealer.storeResource') or environment.containsProperty('idp.sealer.keyStrategy')) ? getObject('shibboleth.DataSealer') : null}" />
@@ -111,7 +135,7 @@
<bean id="c14n/SAML1Transient" parent="shibboleth.AbstractSAML1C14NFlowBean"
p:formats="#{ {'urn:mace:shibboleth:1.0:nameIdentifier'} }">
<property name="implementation">
- <bean id="NameIdentifierCanonicalization/SAML1Transient" class="net.shibboleth.idp.saml.nameid.impl.NameIdentifierCanonicalization">
+ <bean id="NameIDC14N/SAML1Transient" class="net.shibboleth.idp.saml.nameid.impl.NameIdentifierCanonicalization">
<property name="decoder">
<bean class="net.shibboleth.idp.saml.nameid.impl.TransientNameIdentifierDecoder"
p:idStore-ref="shibboleth.StorageService" />
@@ -123,7 +147,7 @@
<bean id="c14n/SAML1CryptoTransient" parent="shibboleth.AbstractSAML1C14NFlowBean"
p:formats="#{ {'urn:mace:shibboleth:1.0:nameIdentifier'} }">
<property name="implementation">
- <bean id="NameIdentifierCanonicalization/SAML1CryptoTransient" class="net.shibboleth.idp.saml.nameid.impl.NameIdentifierCanonicalization">
+ <bean id="NameIDC14N/SAML1CryptoTransient" class="net.shibboleth.idp.saml.nameid.impl.NameIdentifierCanonicalization">
<property name="decoder">
<bean class="net.shibboleth.idp.saml.nameid.impl.CryptoTransientNameIdentifierDecoder"
p:dataSealer="#{(environment.containsProperty('idp.sealer.storeResource') or environment.containsProperty('idp.sealer.keyStrategy')) ? getObject('shibboleth.DataSealer') : null}" />
@@ -142,7 +166,7 @@
p:activationCondition-ref="shibboleth.NameTransformPredicate"
p:formats-ref="shibboleth.NameTransformFormats">
<property name="implementation">
- <bean id="NameIDCanonicalization/SAML2Transform" class="net.shibboleth.idp.saml.nameid.impl.NameIDCanonicalization"
+ <bean id="NameIDC14N/SAML2Transform" class="net.shibboleth.idp.saml.nameid.impl.NameIDCanonicalization"
p:decoder="#{getObject('shibboleth.SAML2Transform.NameIDDecoder') ?: getObject('TransformingNameIDDecoder')}" />
</property>
</bean>
@@ -157,7 +181,7 @@
p:activationCondition-ref="shibboleth.ProxyNameTransformPredicate"
p:formats-ref="shibboleth.ProxyNameTransformFormats">
<property name="implementation">
- <bean id="NameIDCanonicalization/SAML2ProxyTransform" class="net.shibboleth.idp.saml.nameid.impl.NameIDCanonicalization"
+ <bean id="NameIDC14N/SAML2ProxyTransform" class="net.shibboleth.idp.saml.nameid.impl.NameIDCanonicalization"
p:decoder="#{getObject('shibboleth.SAML2ProxyTransform.NameIDDecoder') ?: getObject('ProxyTransformingNameIDDecoder')}" />
</property>
</bean>
@@ -172,7 +196,7 @@
p:activationCondition-ref="shibboleth.NameTransformPredicate"
p:formats-ref="shibboleth.NameTransformFormats">
<property name="implementation">
- <bean id="NameIdentifierCanonicalization/SAML1Transform" class="net.shibboleth.idp.saml.nameid.impl.NameIdentifierCanonicalization"
+ <bean id="NameIDC14N/SAML1Transform" class="net.shibboleth.idp.saml.nameid.impl.NameIdentifierCanonicalization"
p:decoder="#{getObject('shibboleth.SAML1Transform.NameIdentifierDecoder') ?: getObject('TransformingNameIdentifierDecoder')}" />
</property>
</bean>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/attribute-sourced-subject-c14n-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/attribute-sourced-subject-c14n-beans.xml
index 538de7961..2fb0e2ab9 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/attribute-sourced-subject-c14n-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/attribute-sourced-subject-c14n-beans.xml
@@ -34,7 +34,7 @@
</bean>
<bean id="AttributeSourcedSubjectCanonicalization"
- class="net.shibboleth.idp.authn.impl.AttributeSourcedSubjectCanonicalization" scope="prototype"
+ class="net.shibboleth.idp.authn.impl.AttributeSourcedSubjectCanonicalizationAction" scope="prototype"
p:attributeSourceIds-ref="shibboleth.c14n.attribute.AttributeSourceIds"
p:resolveFromSubject="%{idp.c14n.attribute.resolveFromSubject:false}"
p:lowercase="#{getObject('shibboleth.c14n.attribute.Lowercase') ?: %{idp.c14n.attribute.lowercase:false}}"
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list