[java-idp-plugin-oidc-op-oidfed] branch main updated: Initial implementation of attribute filter policy rule for entity trust marks
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Oct 10 16:58:20 UTC 2025
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch main
in repository java-idp-plugin-oidc-op-oidfed.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-op-oidfed.git;a=commit;h=9bbb2e57ff4a4430a7d4fc1d07cbfe63a97a1487
The following commit(s) were added to refs/heads/main by this push:
new 9bbb2e5 Initial implementation of attribute filter policy rule for entity trust marks
9bbb2e5 is described below
commit 9bbb2e57ff4a4430a7d4fc1d07cbfe63a97a1487
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Oct 10 19:58:04 2025 +0300
Initial implementation of attribute filter policy rule for entity trust marks
- Schema 'urn:mace:shibboleth:2.0:afp:oidfed'
- Flow test case to issue email-claim only if entity has 'https://example.org/email-allowing-trust-mark' trust mark
- Also fixed and improved trust mark parsing according to the current spec
---
idp-oidfed-op-impl/pom.xml | 10 +-
.../policyrule/impl/EntityTrustMarkPolicyRule.java | 139 +++++++++++++++++++++
.../impl/AttributeFilterNamespaceHandler.java | 35 ++++++
.../impl/EntityTrustMarkPolicyRuleParser.java | 40 ++++++
...DefaultPayloadJOSEObjectCredentialResolver.java | 9 +-
...DefaultTrustChainTrustMarksParsingStrategy.java | 80 ++++++++++--
.../op/oidfed/profile/impl/ResolveTrustMarks.java | 21 +++-
.../oidfed/metadata-lookup-ext-oidfed-beans.xml | 4 +
.../idp/flows/oidfed/register/register-beans.xml | 4 +
.../oidfed/resolve-entity/resolve-entity-beans.xml | 4 +
.../src/main/resources/META-INF/spring.handlers | 1 +
.../src/main/resources/META-INF/spring.schemas | 2 +
.../resources/schema/shibboleth-afp-oidfed.xsd | 24 ++++
.../flow/oidfed/AbstractFederationFlowTest.java | 86 +++++++++----
.../UserInfoFlowAutomaticRegistrationTest.java | 86 +++++++++++++
.../idp/module/conf/attribute-filter.xml | 8 +-
16 files changed, 507 insertions(+), 46 deletions(-)
diff --git a/idp-oidfed-op-impl/pom.xml b/idp-oidfed-op-impl/pom.xml
index e71326d..6557347 100644
--- a/idp-oidfed-op-impl/pom.xml
+++ b/idp-oidfed-op-impl/pom.xml
@@ -265,6 +265,11 @@
<artifactId>shib-attribute-filter-api</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>${shib-attribute.groupId}</groupId>
+ <artifactId>shib-attribute-filter-spring</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
@@ -362,11 +367,6 @@
<artifactId>shib-attribute-filter-impl</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>${shib-shared.groupId}</groupId>
- <artifactId>shib-attribute-filter-spring</artifactId>
- <scope>test</scope>
- </dependency>
<dependency>
<groupId>${opensaml.groupId}</groupId>
<artifactId>opensaml-messaging-impl</artifactId>
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/policyrule/impl/EntityTrustMarkPolicyRule.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/policyrule/impl/EntityTrustMarkPolicyRule.java
new file mode 100644
index 0000000..c7dcc0d
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/policyrule/impl/EntityTrustMarkPolicyRule.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.policyrule.impl;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.RecursiveTypedParentContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.filter.PolicyRequirementRule;
+import net.shibboleth.idp.attribute.filter.context.AttributeFilterContext;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Compare the verified trust mark IDs for the entity with the provided value.
+ */
+public class EntityTrustMarkPolicyRule extends AbstractIdentifiableInitializableComponent
+ implements PolicyRequirementRule {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(EntityTrustMarkPolicyRule.class);
+
+ /** Lookup strategy to locate trust chain context. */
+ @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+ /** String to match for a positive evaluation. */
+ @Nullable private String matchString;
+
+ /** The String used to prefix log message. */
+ @Nullable private String logPrefix;
+
+ /**
+ * Constructor.
+ */
+ public EntityTrustMarkPolicyRule() {
+ final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+ new ChildContextLookup<>(RelyingPartyTrustChainContext.class, false).compose(
+ new InboundMessageContextLookup());
+ assert tcls != null;
+ trustChainContextLookupStrategy = tcls;
+ logPrefix = null;
+ }
+
+ /**
+ * Set the lookup strategy to locate trust chain context.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setTrustChainContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, RelyingPartyTrustChainContext> strategy) {
+ checkSetterPreconditions();
+ trustChainContextLookupStrategy =
+ Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
+ }
+
+ /**
+ * Sets the string to match for a positive evaluation.
+ *
+ * @param match string to match for a positive evaluation
+ */
+ public void setMatchString(@Nullable final String match) {
+ checkSetterPreconditions();
+ matchString = match;
+ }
+
+ /**
+ * Return a string which is to be prepended to all log messages.
+ *
+ * @return "Attribute Filter '<filterID>' :"
+ */
+ @Nonnull protected String getLogPrefix() {
+ // local cache of cached entry to allow unsynchronised clearing.
+ String prefix = logPrefix;
+ if (null == prefix) {
+ final StringBuilder builder = new StringBuilder("Attribute Filter '").append(getId()).append("':");
+ prefix = builder.toString();
+ if (null == logPrefix) {
+ logPrefix = prefix;
+ }
+ }
+
+ assert prefix != null;
+ return prefix;
+ }
+
+ /**
+ * Compare the authentication request scopes with the provided string.
+ *
+ * @param filterContext the context
+ * @return whether it matches
+ */
+ @Override
+ public Tristate matches(@Nonnull final AttributeFilterContext filterContext) {
+ checkComponentActive();
+ final RelyingPartyTrustChainContext trustChainContext = trustChainContextLookupStrategy.apply(
+ new RecursiveTypedParentContextLookup<>(ProfileRequestContext.class).apply(filterContext));
+ if (trustChainContext == null) {
+ log.debug("{} No RelyingPartyTrustChainContext resolved", getLogPrefix());
+ return Tristate.FALSE;
+ }
+ final String entityId = Optional.ofNullable(trustChainContext.getSelectedTrustChain())
+ .map(pair -> pair.getFirst())
+ .map(chain -> chain.get(0))
+ .map(statement -> statement.getEntityID().getValue())
+ .orElse(null);
+ if (entityId == null) {
+ log.debug("{} Could not resolve entity ID from selected trust chain", getLogPrefix());
+ return Tristate.FALSE;
+ }
+ final Boolean result = Optional.ofNullable(trustChainContext.getVerifiedTrustMarkIds())
+ .map(map -> map.get(entityId))
+ .map(ids -> ids.contains(matchString))
+ .orElse(Boolean.FALSE);
+ log.debug("{} Returning {}", getLogPrefix(), result);
+ return result.booleanValue() ? Tristate.TRUE : Tristate.FALSE;
+ }
+}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/spring/impl/AttributeFilterNamespaceHandler.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/spring/impl/AttributeFilterNamespaceHandler.java
new file mode 100644
index 0000000..9036ab8
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/spring/impl/AttributeFilterNamespaceHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.spring.impl;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.spring.policyrule.impl.EntityTrustMarkPolicyRuleParser;
+import net.shibboleth.shared.spring.custom.BaseSpringNamespaceHandler;
+
+/**
+ * Namespace handler for the OpenID federation specific attribute filter engine functions.
+ */
+public class AttributeFilterNamespaceHandler extends BaseSpringNamespaceHandler {
+
+ /** OIDFed namespace. */
+ public static final String NAMESPACE = "urn:mace:shibboleth:2.0:afp:oidfed";
+
+ /** {@inheritDoc} */
+ @Override
+ public void init() {
+ // Policy rules
+ registerBeanDefinitionParser(EntityTrustMarkPolicyRuleParser.SCHEMA_TYPE_AFP,
+ new EntityTrustMarkPolicyRuleParser());
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/spring/policyrule/impl/EntityTrustMarkPolicyRuleParser.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/spring/policyrule/impl/EntityTrustMarkPolicyRuleParser.java
new file mode 100644
index 0000000..0891184
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/spring/policyrule/impl/EntityTrustMarkPolicyRuleParser.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.spring.policyrule.impl;
+
+import javax.annotation.Nonnull;
+import javax.xml.namespace.QName;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.policyrule.impl.EntityTrustMarkPolicyRule;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.spring.impl.AttributeFilterNamespaceHandler;
+import net.shibboleth.idp.attribute.filter.spring.policyrule.impl.AbstractStringPolicyRuleParser;
+
+/**
+ * Bean definition parser for {@link EntityTrustMarkPolicyRule}.
+ */
+public class EntityTrustMarkPolicyRuleParser extends AbstractStringPolicyRuleParser {
+
+ /** Schema type. */
+ @Nonnull public static final QName SCHEMA_TYPE_AFP =
+ new QName(AttributeFilterNamespaceHandler.NAMESPACE, "OIDFEDEntityTrustMark");
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ protected Class<EntityTrustMarkPolicyRule> getNativeBeanClass() {
+ return EntityTrustMarkPolicyRule.class;
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultPayloadJOSEObjectCredentialResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultPayloadJOSEObjectCredentialResolver.java
index 99fc68b..895728f 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultPayloadJOSEObjectCredentialResolver.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultPayloadJOSEObjectCredentialResolver.java
@@ -15,6 +15,7 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
import java.text.ParseException;
+import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
@@ -29,6 +30,7 @@ import com.nimbusds.jwt.SignedJWT;
import net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver;
import net.shibboleth.oidc.security.jose.criterion.JOSEObjectCriterion;
+import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.resolver.CriteriaSet;
import net.shibboleth.shared.resolver.ResolverException;
@@ -56,7 +58,12 @@ public class DefaultPayloadJOSEObjectCredentialResolver extends BasicJOSEObjectC
}
try {
final SignedJWT jwt = SignedJWT.parse(joseObject.serialize());
- final JWKSet jwks = JWKSet.parse(jwt.getJWTClaimsSet().getJSONObjectClaim("jwks"));
+ final Map<String, Object> rawJwks = jwt.getJWTClaimsSet().getJSONObjectClaim("jwks");
+ if (rawJwks == null || rawJwks.isEmpty()) {
+ log.debug("No jwks found from the payload");
+ return CollectionSupport.emptyList();
+ }
+ final JWKSet jwks = JWKSet.parse(rawJwks);
return jwks.getKeys().stream()
.filter(Objects::nonNull)
.map(jwk -> buildJWKCredential(jwk, null))
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustMarksParsingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustMarksParsingStrategy.java
index 5f98e06..623ef0b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustMarksParsingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustMarksParsingStrategy.java
@@ -23,14 +23,22 @@ import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.MapType;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.primitive.StringSupport;
@@ -40,16 +48,38 @@ import net.shibboleth.shared.primitive.StringSupport;
*
* TODO: iat / subject validation (switch into using claims validators)
*/
- at ThreadSafe
-public class DefaultTrustChainTrustMarksParsingStrategy
+ at ThreadSafeAfterInit
+public class DefaultTrustChainTrustMarksParsingStrategy extends AbstractIdentifiableInitializableComponent
implements Function<List<EntityStatement>,Map<String,List<SignedJWT>>> {
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainTrustMarksParsingStrategy.class);
+ /** JSON object mapper used for decoding JSON into Map. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+ /**
+ * Set the JSON {@link ObjectMapper} used for decoding JSON into Map.
+ *
+ * @param mapper object mapper
+ */
+ public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+ checkSetterPreconditions();
+
+ objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("Object mapper cannot be null");
+ }
+ }
/** {@inheritDoc} */
@Override @Nullable
public Map<String, List<SignedJWT>> apply(@Nullable final List<EntityStatement> trustChain) {
+ checkComponentActive();
if (trustChain == null || trustChain.size() < 3) {
log.error("Unexpected length in the trust chain: {}", trustChain == null ? "null" : trustChain.size());
return null;
@@ -59,18 +89,18 @@ public class DefaultTrustChainTrustMarksParsingStrategy
for (final EntityStatement statement : trustChain) {
assert statement != null;
final SignedJWT statementJwt = statement.getSignedStatement();
- log.trace("Inspecting entity statement {} with trust marks {}",
- statementJwt.serialize(), statement.getClaimsSet().getTrustMarks());
-
final List<Object> rawTrustMarks = statement.getClaimsSet().getJSONArrayClaim("trust_marks");
+ log.trace("Inspecting entity statement {} with trust marks {}", statementJwt.serialize(), rawTrustMarks);
+
if (rawTrustMarks != null && !rawTrustMarks.isEmpty()) {
+ log.trace("Transforming the trust mark into a JWT");
final List<SignedJWT> trustMarks = rawTrustMarks
.stream()
- .filter(Map.class::isInstance)
- .map(Map.class::cast)
- .map(map -> parseTrustMark(map.get("trust_mark"), map.get("trust_mark_type")))
+ .map(entry -> parseTrustMark(entry))
.filter(Objects::nonNull)
.toList();
+ log.debug("Returning {} trust marks for entity {}", trustMarks.size(),
+ statement.getEntityID().getValue());
result.put(statement.getEntityID().getValue(), trustMarks);
}
}
@@ -78,15 +108,41 @@ public class DefaultTrustChainTrustMarksParsingStrategy
return result;
}
+ /**
+ * Parses and validates JWT from the trust mark entry.
+ *
+ * @param trustMarkEntry trust mark entry as Strign to be parsed into a JWT
+ * @return trust mark JWT if valid, null otherwise
+ */
+ @Nullable private SignedJWT parseTrustMark(@Nullable final Object trustMarkEntry) {
+ if (trustMarkEntry == null) {
+ return null;
+ }
+ final JavaType stringType = objectMapper.constructType(String.class);
+ final MapType stringMapType =
+ objectMapper.getTypeFactory().constructMapType(Map.class, stringType, stringType);
+ try {
+ final Map<String,String> map = objectMapper.readValue(trustMarkEntry.toString(), stringMapType);
+ if (map != null) {
+ log.debug("Parsed trust_mark map {}", map);
+ return verifyTrustMark(map.get("trust_mark"), map.get("trust_mark_type"));
+ }
+ } catch (final JsonProcessingException e) {
+ log.warn("Could not parse trust mark issuers from the trust chain", e);
+ }
+ return null;
+ }
+
/**
* Verifies the trust mark id and issuer claims.
*
- * @param trustMark trust mark to be verified, expected to be parseable from string
+ * @param trustMark trust mark to be verified
* @param id the id to be verified from the JWT claims set
* @return trust mark JWT if valid, null otherwise
*/
- @Nullable private SignedJWT parseTrustMark(@Nullable final Object trustMark, @Nullable final Object id) {
- if (trustMark == null || !(trustMark instanceof String) || id == null || !(id instanceof String)) {
+ @Nullable private SignedJWT verifyTrustMark(@Nullable final String trustMark, @Nullable final String id) {
+ if (trustMark == null || id == null) {
+ log.trace("Could not parse trust mark {} with trust_mark_type {}", trustMark, id);
return null;
}
try {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
index 5c7500a..28cbbbe 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
@@ -40,7 +40,6 @@ import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustMarksParsingStrategy;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityIDCriterion;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityStatementCriterion;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TrustMarkOwnersCriterion;
@@ -78,7 +77,8 @@ public class ResolveTrustMarks extends AbstractProfileAction {
@Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
/** Strategy used to parse trust marks from the selected trust chain. */
- @Nonnull private Function<List<EntityStatement>,Map<String,List<SignedJWT>>> trustChainTrustMarksParsingStrategy;
+ @NonnullAfterInit
+ private Function<List<EntityStatement>,Map<String,List<SignedJWT>>> trustChainTrustMarksParsingStrategy;
/** Strategy used to lookup trusted trust mark issuers for the trust chain. */
@NonnullAfterInit
@@ -128,7 +128,6 @@ public class ResolveTrustMarks extends AbstractProfileAction {
new InboundMessageContextLookup());
assert tcls != null;
trustChainContextLookupStrategy = tcls;
- trustChainTrustMarksParsingStrategy = new DefaultTrustChainTrustMarksParsingStrategy();
trustedTrustMarkIssuersOnlyCondition = PredicateSupport.alwaysTrue();
}
@@ -143,6 +142,18 @@ public class ResolveTrustMarks extends AbstractProfileAction {
trustChainContextLookupStrategy =
Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
}
+
+ /**
+ * Set the strategy used to parse trust marks from the selected trust chain.
+ *
+ * @param strategy parsing strategy
+ */
+ public void setTrustChainTrustMarksParsingStrategy(
+ @Nonnull final Function<List<EntityStatement>,Map<String,List<SignedJWT>>> strategy) {
+ checkSetterPreconditions();
+ trustChainTrustMarksParsingStrategy =
+ Constraint.isNotNull(strategy, "TrustChainTrustMarksParsingStrategy cannot be null");
+ }
/**
* Set the strategy used to lookup trusted trust mark issuers for the trust chain.
@@ -247,6 +258,9 @@ public class ResolveTrustMarks extends AbstractProfileAction {
if (delegationTrustEngine == null) {
throw new ComponentInitializationException("Delegation Trust Engine cannot be null");
}
+ if (trustChainTrustMarksParsingStrategy == null) {
+ throw new ComponentInitializationException("Trust marks parsing strategy cannot be null");
+ }
if (trustedTrustMarkIssuersLookupStrategy == null) {
throw new ComponentInitializationException("Trusted trust mark issuers lookup strategy cannot be null");
}
@@ -476,6 +490,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
final EntityStatement trustMarkIssuer = trustMarkChain.get(0);
assert trustMarkIssuer != null;
final CriteriaSet criteria = new CriteriaSet(new SubjectEntityStatementCriterion(trustMarkIssuer));
+ log.trace("{} Validating entity statement {}", getLogPrefix(), trustMarkIssuer.getSignedStatement().serialize());
try {
if (trustEngine.validate(jwt, criteria)) {
final String id = getTrustMarkId(jwt);
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
index 3c502ef..d8c2f11 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
@@ -110,6 +110,10 @@
p:delegatedTrustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.DelegatedTrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}"
p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine"
p:delegationTrustEngine-ref="shibboleth.oidfed.DefaultDelegatedTrustMarkTrustEngine">
+ <property name="trustChainTrustMarksParsingStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustMarksParsingStrategy"
+ p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
+ </property>
<property name="trustedTrustMarkIssuersLookupStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy"
p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
index bd090cc..33283ad 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
@@ -188,6 +188,10 @@
p:delegatedTrustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.DelegatedTrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}"
p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine"
p:delegationTrustEngine-ref="shibboleth.oidfed.DefaultDelegatedTrustMarkTrustEngine">
+ <property name="trustChainTrustMarksParsingStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustMarksParsingStrategy"
+ p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
+ </property>
<property name="trustedTrustMarkIssuersLookupStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy"
p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
index ee74c7e..cf45574 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
@@ -131,6 +131,10 @@
p:delegatedTrustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.DelegatedTrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}"
p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine"
p:delegationTrustEngine-ref="shibboleth.oidfed.DefaultDelegatedTrustMarkTrustEngine">
+ <property name="trustChainTrustMarksParsingStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustMarksParsingStrategy"
+ p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
+ </property>
<property name="trustedTrustMarkIssuersLookupStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy"
p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/spring.handlers b/idp-oidfed-op-impl/src/main/resources/META-INF/spring.handlers
new file mode 100644
index 0000000..7e9c56c
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/spring.handlers
@@ -0,0 +1 @@
+urn\:mace\:shibboleth\:2.0\:afp\:oidfed = net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.spring.impl.AttributeFilterNamespaceHandler
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/spring.schemas b/idp-oidfed-op-impl/src/main/resources/META-INF/spring.schemas
new file mode 100644
index 0000000..85b0d7c
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/spring.schemas
@@ -0,0 +1,2 @@
+classpath\:/schema/shibboleth-afp-oidfed.xsd = schema/shibboleth-afp-oidfed.xsd
+http\://shibboleth.net/schema/oidfed/shibboleth-afp-oidfed.xsd = schema/shibboleth-afp-oidfed.xsd
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/resources/schema/shibboleth-afp-oidfed.xsd b/idp-oidfed-op-impl/src/main/resources/schema/shibboleth-afp-oidfed.xsd
new file mode 100644
index 0000000..5f57c35
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/resources/schema/shibboleth-afp-oidfed.xsd
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:afp="urn:mace:shibboleth:2.0:afp"
+ targetNamespace="urn:mace:shibboleth:2.0:afp:oidfed"
+ elementFormDefault="qualified">
+
+ <import namespace="urn:mace:shibboleth:2.0:afp"
+ schemaLocation="http://shibboleth.net/schema/idp/shibboleth-afp.xsd" />
+
+ <annotation>
+ <documentation>Schema for the OIDFed extension attribute filter policies.</documentation>
+ </annotation>
+
+ <complexType name="OIDFEDEntityTrustMark">
+ <annotation>
+ <documentation>
+ A match function that matches the verified trust mark IDs of the entity against the specified value.
+ </documentation>
+ </annotation>
+ <complexContent>
+ <extension base="afp:StringMatchType" />
+ </complexContent>
+ </complexType>
+
+</schema>
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
index fe61a3d..eaa0934 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
@@ -79,6 +79,7 @@ import net.minidev.json.JSONObject;
import net.shibboleth.idp.plugin.oidc.op.oidfed.TrustChainTestUtil;
import net.shibboleth.idp.plugin.oidc.op.profile.flow.AbstractOidcFlowTest;
import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.logic.Constraint;
/**
@@ -97,6 +98,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
final String anchorId = "https://trust-anchor.federation.local";
final String anchorFetchEndpoint = anchorId + "/fetch";
final String anchorResolveEndpoint = anchorId + "/resolve";
+ final String trustMarkIssuerId = "https://trust-mark-issuer.federation.local";
String issuer = "https://op.example.org";
JWK rpKey;
@@ -104,6 +106,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
JWK anchorKey;
JWK trustedAnchorKey;
JWK intermediateKey;
+ JWK trustMarkIssuerKey;
String subject = "jdoe";
@@ -126,6 +129,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
.keyID("locallyTrustedAnchorKey")
.build();
intermediateKey = initializeNewJwk("RSA", 2048, "mockIntermediateKey");
+ trustMarkIssuerKey = initializeNewJwk("RSA", 2048, "mockTrustMarkIssuerKey");
}
@BeforeMethod
@@ -226,16 +230,23 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
protected String rpEntityConfiguration(final String clientId, final OIDCClientMetadata metadata,
final String... authorityHints) throws URISyntaxException {
- final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(clientId).subject(clientId)
+ return rpEntityConfiguration(clientId, metadata, null, authorityHints);
+ }
+
+ protected String rpEntityConfiguration(final String clientId, final OIDCClientMetadata metadata,
+ final List<Map<String, String>> trustMarks, final String... authorityHints) throws URISyntaxException {
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(clientId).subject(clientId)
.issueTime(Date.from(Instant.now()))
.expirationTime(Date.from(Instant.now().plusSeconds(300)))
.claim("jwks", new JWKSet(leafKey).toJSONObject(true))
.claim("metadata", Map.of("openid_relying_party", metadata.toJSONObject()))
.claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
- new String[] { anchorId } : authorityHints)
- .build();
+ new String[] { anchorId } : authorityHints);
+ if (trustMarks != null) {
+ builder.claim("trust_marks", trustMarks);
+ }
final EntityStatement rpConfiguration =
- TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, claimsSet);
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, builder.build());
return rpConfiguration.getSignedStatement().serialize();
}
@@ -254,6 +265,20 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
return rpConfiguration.getSignedStatement().serialize();
}
+ protected String trustMarkIssuerConfiguration(final String entityId, final String... authorityHints) {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(trustMarkIssuerKey).toJSONObject(true))
+ .claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
+ new String[] { anchorId } : authorityHints)
+ .claim("metadata", Map.of("federation_entity", CollectionSupport.emptyMap()))
+ .build();
+ final EntityStatement rpConfiguration =
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustMarkIssuerKey, claimsSet);
+ return rpConfiguration.getSignedStatement().serialize();
+ }
+
protected String opEntityConfiguration(final String issuer, final String... authorityHints)
throws URISyntaxException {
return opEntityConfiguration(issuer, emptyOpMetadata(issuer), authorityHints);
@@ -290,31 +315,32 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
}
protected String trustedAnchorConfiguration() {
- final String anchorId = "https://trust-anchor.federation.local";
- final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(anchorId).subject(anchorId)
- .issueTime(Date.from(Instant.now()))
- .expirationTime(Date.from(Instant.now().plusSeconds(300)))
- .claim("jwks", new JWKSet(trustedAnchorKey).toJSONObject(true))
- .claim("metadata", Map.of("federation_entity", Map.of("federation_fetch_endpoint",
- anchorFetchEndpoint, "federation_resolve_endpoint", anchorResolveEndpoint)))
- .build();
- final EntityStatement anchorConfiguration =
- TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustedAnchorKey, claimsSet);
- return anchorConfiguration.getSignedStatement().serialize();
+ return trustedAnchorConfiguration(null);
}
protected String trustedAnchorConfiguration(final Map<String, Object> constraints) {
+ return trustedAnchorConfiguration(constraints,
+ Map.of("https://example.org/email-allowing-trust-mark", List.of(trustMarkIssuerId)));
+ }
+
+ protected String trustedAnchorConfiguration(final Map<String, Object> constraints,
+ final Map<String,List<String>> trustMarkIssuers) {
+
final String anchorId = "https://trust-anchor.federation.local";
- final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(anchorId).subject(anchorId)
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(anchorId)
.issueTime(Date.from(Instant.now()))
.expirationTime(Date.from(Instant.now().plusSeconds(300)))
.claim("jwks", new JWKSet(trustedAnchorKey).toJSONObject(true))
.claim("metadata", Map.of("federation_entity", Map.of("federation_fetch_endpoint",
- anchorFetchEndpoint)))
- .claim("constraints", constraints)
- .build();
+ anchorFetchEndpoint, "federation_resolve_endpoint", anchorResolveEndpoint)));
+ if (constraints != null) {
+ builder.claim("constraints", constraints);
+ }
+ if (trustMarkIssuers != null) {
+ builder.claim("trust_mark_issuers", trustMarkIssuers);
+ }
final EntityStatement anchorConfiguration =
- TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustedAnchorKey, claimsSet);
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustedAnchorKey, builder.build());
return anchorConfiguration.getSignedStatement().serialize();
}
@@ -378,6 +404,11 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
}
protected String rpResolveEntityResponse(final String clientId, final Map<String, Object> metadata) {
+ return rpResolveEntityResponse(clientId, metadata, null);
+ }
+
+ protected String rpResolveEntityResponse(final String clientId, final Map<String, Object> metadata,
+ final List<Map<String, String>> trustMarks) {
final List<String> trustChain;
try {
trustChain = List.of(rpEntityConfiguration(clientId),
@@ -393,6 +424,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
.expirationTime(Date.from(Instant.now().plusSeconds(300)))
.claim("trust_chain", trustChain)
.claim("metadata", metadata)
+ .claim("trust_marks", trustMarks)
.build();
return TrustChainTestUtil.signedJwt(JWSAlgorithm.RS256, trustedAnchorKey, "application/resolve-response+jwt",
claimsSet).serialize();
@@ -441,9 +473,14 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
}
protected void rpConfigureMockHttpClient(final String clientId, final String rpEntityConfiguration) {
+ rpConfigureMockHttpClient(clientId, rpEntityConfiguration, trustedAnchorConfiguration());
+ }
+
+ protected void rpConfigureMockHttpClient(final String clientId, final String rpEntityConfiguration,
+ final String trustedAnchorConfiguration) {
try {
mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration));
- mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration));
mapResponse(subordinateStatementUrl(anchorFetchEndpoint, clientId),
mockResponse(subordinateStatement(clientId, Map.of("openid_relying_party",
new OIDCClientMetadata().toJSONObject()))));
@@ -473,11 +510,16 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
}
protected void rpResolveEntityConfigureMockHttpClient(final String clientId, final OIDCClientMetadata metadata) {
+ rpResolveEntityConfigureMockHttpClient(clientId, metadata, null);
+ }
+
+ protected void rpResolveEntityConfigureMockHttpClient(final String clientId, final OIDCClientMetadata metadata,
+ final List<Map<String, String>> trustMarks) {
try {
mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
mapResponse(resolveEntityUrl(anchorResolveEndpoint, clientId, anchorId),
mockResponse(rpResolveEntityResponse(clientId, Map.of("openid_relying_party",
- metadata.toJSONObject()))));
+ metadata.toJSONObject()), trustMarks)));
} catch (UnsupportedOperationException | IOException e) {
Assert.fail("Could not initialize mock HTTP client", e);
}
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
index c692231..33b4d69 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
@@ -20,6 +20,7 @@ import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.List;
+import java.util.Map;
import org.opensaml.storage.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -29,6 +30,8 @@ import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.oauth2.sdk.OAuth2Error;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.id.ClientID;
@@ -36,11 +39,14 @@ import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.oauth2.sdk.token.BearerTokenError;
import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.TrustChainTestUtil;
import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport;
import net.shibboleth.idp.plugin.oidc.op.profile.flow.UserInfoTest;
import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.security.DataSealerException;
@@ -77,6 +83,42 @@ public class UserInfoFlowAutomaticRegistrationTest extends AbstractFederationFlo
Assert.assertNull(response.getUserInfoJWT());
}
+ @Test
+ public void testSuccess_extraClaimViaTrustMark() throws URISyntaxException, NoSuchAlgorithmException,
+ DataSealerException, ComponentInitializationException, IOException {
+ final String clientId = uniqueClientId();
+ final String trustMark = TrustChainTestUtil.trustMark(JWSAlgorithm.RS256, trustMarkIssuerKey, trustMarkIssuerId,
+ clientId, "https://example.org/email-allowing-trust-mark", Instant.now().plusSeconds(300)).serialize();
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ final String rpEntityConfiguration = rpEntityConfiguration(clientId, metadata, List.of(Map.of(
+ "trust_mark_type", "https://example.org/email-allowing-trust-mark",
+ "trust_mark", trustMark)));
+ final String trustAnchorConfiguration = trustedAnchorConfiguration();
+ rpConfigureMockHttpClient(clientId, rpEntityConfiguration, trustAnchorConfiguration);
+ try {
+ mapResponse(entityConfigurationUrl(trustMarkIssuerId),
+ mockResponse(trustMarkIssuerConfiguration(trustMarkIssuerId)));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, trustMarkIssuerId),
+ mockResponse(subordinateStatement(trustMarkIssuerId,
+ Map.of("federation_entity", CollectionSupport.emptyMap()))));
+ } catch (UnsupportedOperationException | IOException e) {
+ Assert.fail("Could not initialize mock HTTP client", e);
+ }
+
+ final BearerAccessToken token = buildToken(clientId, List.of(clientId, anchorId));
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(UserInfoTest.FLOW_ID, null, externalContext);
+ final UserInfoSuccessResponse response = parseSuccessResponse(result, UserInfoSuccessResponse.class);
+ Assert.assertEquals(response.getUserInfo().getSubject().getValue(), subject);
+ final UserInfo userInfo = response.getUserInfo();
+ Assert.assertNotNull(userInfo);
+ Assert.assertEquals(userInfo.getEmailAddress(), "jdoe at example.org");
+ Assert.assertNull(userInfo.getNickname());
+ Assert.assertNull(response.getUserInfoJWT());
+ }
+
@Test
public void testSuccess_resolveApi() throws Exception {
final String clientId = uniqueClientId();
@@ -94,6 +136,50 @@ public class UserInfoFlowAutomaticRegistrationTest extends AbstractFederationFlo
Assert.assertNull(response.getUserInfoJWT());
}
+ @Test
+ public void testSuccess_resolveApi_extraClaimViaTrustMark() throws Exception {
+ final String clientId = uniqueClientId();
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(URI.create(redirectUri));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ final String trustMark = TrustChainTestUtil.trustMark(JWSAlgorithm.RS256, anchorKey, anchorId,
+ clientId, "https://example.org/email-allowing-trust-mark", Instant.now().plusSeconds(300)).serialize();
+ rpResolveEntityConfigureMockHttpClient(clientId, metadata, List.of(Map.of(
+ "trust_mark_type", "https://example.org/email-allowing-trust-mark",
+ "trust_mark", trustMark)));
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ final BearerAccessToken token = buildToken(clientId, List.of(clientId, anchorId));
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(UserInfoTest.FLOW_ID, null, externalContext);
+ final UserInfoSuccessResponse response = parseSuccessResponse(result, UserInfoSuccessResponse.class);
+ Assert.assertEquals(response.getUserInfo().getSubject().getValue(), subject);
+ final UserInfo userInfo = response.getUserInfo();
+ Assert.assertNotNull(userInfo);
+ Assert.assertEquals(userInfo.getEmailAddress(), "jdoe at example.org");
+ }
+
+ @Test
+ public void testSuccess_resolveApi_noExtraClaimViaAuthorityTrustMark() throws Exception {
+ final String clientId = uniqueClientId();
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(URI.create(redirectUri));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ final String trustMark = TrustChainTestUtil.trustMark(JWSAlgorithm.RS256, anchorKey, anchorId,
+ anchorId, "https://example.org/email-allowing-trust-mark", Instant.now().plusSeconds(300)).serialize();
+ rpResolveEntityConfigureMockHttpClient(clientId, metadata, List.of(Map.of(
+ "trust_mark_type", "https://example.org/email-allowing-trust-mark",
+ "trust_mark", trustMark)));
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ final BearerAccessToken token = buildToken(clientId, List.of(clientId, anchorId));
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(UserInfoTest.FLOW_ID, null, externalContext);
+ final UserInfoSuccessResponse response = parseSuccessResponse(result, UserInfoSuccessResponse.class);
+ Assert.assertEquals(response.getUserInfo().getSubject().getValue(), subject);
+ final UserInfo userInfo = response.getUserInfo();
+ Assert.assertNotNull(userInfo);
+ Assert.assertNull(userInfo.getEmailAddress());
+ }
+
@Test
public void testFails_resolveApiFails_noFallback() throws Exception {
final String clientId = uniqueClientId();
diff --git a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-filter.xml b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-filter.xml
index a8e9921..606841a 100644
--- a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-filter.xml
+++ b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-filter.xml
@@ -13,8 +13,10 @@
xmlns="urn:mace:shibboleth:2.0:afp"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oidc="urn:mace:shibboleth:2.0:afp:oidc"
+ xmlns:oidfed="urn:mace:shibboleth:2.0:afp:oidfed"
xsi:schemaLocation="urn:mace:shibboleth:2.0:afp http://shibboleth.net/schema/idp/shibboleth-afp.xsd
- urn:mace:shibboleth:2.0:afp:oidc http://shibboleth.net/schema/oidc/shibboleth-afp-oidc.xsd">
+ urn:mace:shibboleth:2.0:afp:oidc http://shibboleth.net/schema/oidc/shibboleth-afp-oidc.xsd
+ urn:mace:shibboleth:2.0:afp:oidfed http://shibboleth.net/schema/oidfed/shibboleth-afp-oidfed.xsd">
<!--
@@ -129,8 +131,8 @@
</AttributeRule>
</AttributeFilterPolicy>
- <AttributeFilterPolicy id="OPENID_SCOPE_EMAIL">
- <PolicyRequirementRule xsi:type="oidc:OIDCScope" value="email" />
+ <AttributeFilterPolicy id="OIDFED_EMAIL">
+ <PolicyRequirementRule xsi:type="oidfed:OIDFEDEntityTrustMark" value="https://example.org/email-allowing-trust-mark" />
<AttributeRule attributeID="mail">
<PermitValueRule xsi:type="ANY" />
</AttributeRule>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list