[java-idp-oidc] 03/03: JOIDC-222 - Support for OpenID Federation
Henri Mikkonen
henri.mikkonen at iki.fi
Fri May 9 13:10:20 UTC 2025
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=a035117c0077e08064858acdde9be4d8bc490587
commit a035117c0077e08064858acdde9be4d8bc490587
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri May 9 16:09:49 2025 +0300
JOIDC-222 - Support for OpenID Federation
https://shibboleth.atlassian.net/browse/JOIDC-222
- Wired the updated metadata policy logic into the automatic and explicit registration, and resolve-entity
- Wired Connect2Id's metadata policy test vectors to the PAR flow tests
---
.../oidfed/profile/impl/OidFederationEventIds.java | 10 ++
.../op/oidfed/profile/impl/ResolveTrustChains.java | 28 ++++--
...ultTrustChainMetadataPolicyMergingStrategy.java | 74 +++++++++-----
.../META-INF/net.shibboleth.idp/postconfig.xml | 47 +++++++++
.../oidc/metadata-lookup/metadata-lookup-beans.xml | 11 ++-
.../idp/flows/oidfed/register/register-beans.xml | 21 ++--
.../oidfed/resolve-entity/resolve-entity-beans.xml | 23 +++--
.../oidc/op/profile/flow/AbstractOidcFlowTest.java | 26 ++++-
.../flow/oidfed/AbstractFederationFlowTest.java | 107 +++++++++++++++++++--
...shedAuthorizeFlowAutomaticRegistrationTest.java | 37 ++++++-
10 files changed, 324 insertions(+), 60 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
index a23864ce..89551686 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
@@ -43,4 +43,14 @@ public class OidFederationEventIds {
* ID of event returned if the given subject is invalid.
*/
@Nonnull @NotEmpty public static final String INVALID_SUBJECT = "InvalidSubject";
+
+ /**
+ * ID of event returned if the given trust anchor is invalid.
+ */
+ @Nonnull @NotEmpty public static final String INVALID_METADATA_POLICY = "InvalidMetadataPolicy";
+
+ /**
+ * ID of event returned if the given trust anchor is invalid.
+ */
+ @Nonnull @NotEmpty public static final String INVALID_METADATA_AGAINST_POLICY = "InvalidMetadataAgainstPolicy";
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
index a9eacb53..ae3bd565 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
@@ -53,6 +53,7 @@ import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.collection.Pair;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.resolver.CriteriaSet;
@@ -258,20 +259,27 @@ public class ResolveTrustChains extends AbstractProfileAction {
trustChainContext.setResolvedTrustChains(cacheResult.get(0));
final List<Pair<List<EntityStatement>, OIDCClientInformation>> policyCompliantChains = new ArrayList<>();
+ String errorEventId = null;
for (final List<EntityStatement> chain : cacheResult.get(0)) {
if (!preSelectedChain.isEmpty() && !preSelectedChain.equals(trustChainIDsLookupStrategy.apply(chain))) {
log.debug("{} Ignored resolved trust chain that doesn't match with preselected chain", getLogPrefix());
continue;
}
- final Map<String, MetadataPolicy> mergedPolicies =
- metadataPolicyMergingStrategy.apply(chain, EntityType.OPENID_RELYING_PARTY.getValue());
- log.debug("{} Merged policy for chain {}", getLogPrefix(), mergedPolicies);
+ final Map<String, MetadataPolicy> mergedPolicies;
+ try {
+ mergedPolicies = metadataPolicyMergingStrategy.apply(chain,
+ EntityType.OPENID_RELYING_PARTY.getValue());
+ log.debug("{} Merged policy for chain {}", getLogPrefix(), mergedPolicies);
+ } catch (final ConstraintViolationException e) {
+ log.warn("{} Could not merge metadata policies", getLogPrefix(), e);
+ errorEventId = OidFederationEventIds.INVALID_METADATA_POLICY;
+ continue;
+ }
assert chain != null;
final OIDCClientMetadata metadata = metadataLookupStrategy.apply(chain);
if (metadata != null) {
final OIDCClientInformation clientInformation = new OIDCClientInformation(
new ClientID(chain.get(0).getEntityID().getValue()), metadata);
- boolean compliant = true;
final JSONObject requestMetadata = clientInformation.toJSONObject();
for (final String claim : mergedPolicies.keySet()) {
final MetadataPolicy policy = mergedPolicies.get(claim);
@@ -282,7 +290,7 @@ public class ResolveTrustChains extends AbstractProfileAction {
final Boolean enforcerResult = mergeResult != null ? mergeResult.getSecond() : null;
if (enforcerResult == null || !enforcerResult.booleanValue()) {
log.warn("{} Metadata claim {} is not compliant with the policy", getLogPrefix(), claim);
- compliant = false;
+ errorEventId = OidFederationEventIds.INVALID_METADATA_AGAINST_POLICY;
} else {
log.trace("{} Validation result is OK for claim {}", getLogPrefix(), claim);
final Object enforcedValue = mergeResult != null ? mergeResult.getFirst() : null;
@@ -290,7 +298,7 @@ public class ResolveTrustChains extends AbstractProfileAction {
}
}
- if (!compliant) {
+ if (errorEventId != null) {
log.warn("{} The requested metadata is not compliant with the policy", getLogPrefix());
} else {
log.debug("{} The requested metadata is compliant with the policy", getLogPrefix());
@@ -306,6 +314,14 @@ public class ResolveTrustChains extends AbstractProfileAction {
}
}
+ if (policyCompliantChains.isEmpty()) {
+ if (errorEventId == null) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ } else {
+ ActionSupport.buildEvent(profileRequestContext, errorEventId);
+ }
+ return;
+ }
log.debug("{} Setting the policy compliant trust chains into the context: {}", getLogPrefix(),
policyCompliantChains);
trustChainContext.setPolicyCompliantTrustChains(policyCompliantChains);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainMetadataPolicyMergingStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainMetadataPolicyMergingStrategy.java
index 513e307a..3477420d 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainMetadataPolicyMergingStrategy.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainMetadataPolicyMergingStrategy.java
@@ -26,18 +26,17 @@ import javax.annotation.Nullable;
import org.slf4j.Logger;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.MapType;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import com.nimbusds.openid.connect.sdk.federation.entities.EntityType;
-import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyViolationException;
-import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataPolicyMergingStrategy;
import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.collection.Pair;
import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.logic.ConstraintViolationException;
import net.shibboleth.shared.primitive.LoggerFactory;
@@ -54,19 +53,15 @@ public class DefaultTrustChainMetadataPolicyMergingStrategy extends AbstractIden
@Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainMetadataPolicyMergingStrategy.class);
/** The strategy used for merging two metadata policies. */
- @Nonnull private BiFunction<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>,
+ @NonnullAfterInit private BiFunction<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>,
Pair<Map<String, MetadataPolicy>, Boolean>> metadataPolicyMergingStrategy;
/** The strategy used for local (additional) metadata policy. */
@NonnullAfterInit private Function<List<EntityStatement>, Map<String, MetadataPolicy>>
localMetadataPolicyStrategy;
- /**
- * Constructor.
- */
- public DefaultTrustChainMetadataPolicyMergingStrategy() {
- metadataPolicyMergingStrategy = new DefaultMetadataPolicyMergingStrategy();
- }
+ /** Object mapper used for deserializing metadata policies. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
/**
* Set the strategy used for merging two metadata policies.
@@ -92,6 +87,31 @@ public class DefaultTrustChainMetadataPolicyMergingStrategy extends AbstractIden
"Local metadata policy strategy cannot be null");
}
+ /**
+ * Set the object mapper used for deserializing metadata policies
+ *
+ * @param mapper What to set.
+ */
+ 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 (metadataPolicyMergingStrategy == null) {
+ throw new ComponentInitializationException("Metadata policy merging strategy cannot be null");
+ }
+ if (localMetadataPolicyStrategy == null) {
+ throw new ComponentInitializationException("Local metadata policy strategy cannot be null");
+ }
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("Object mapper cannot be null");
+ }
+ }
+
/** {@inheritDoc} */
@Override @Nonnull
public Map<String, MetadataPolicy> apply(@Nullable final List<EntityStatement> trustChain,
@@ -153,21 +173,27 @@ public class DefaultTrustChainMetadataPolicyMergingStrategy extends AbstractIden
* @throws ConstraintViolationException if the map of policies could not be parsed from the entity statement
*/
@Nullable
- protected Map<String, MetadataPolicy> parseMetadataPolicy(@Nonnull final EntityStatement entityStatement,
+ protected Map<String, MetadataPolicy> parseMetadataPolicy(
+ @Nonnull final EntityStatement entityStatement,
@Nonnull final String entityType) throws ConstraintViolationException {
- final ObjectMapper objectMapper = new ObjectMapper();
- final TypeReference<HashMap<String,MetadataPolicy>> typeRef =
- new TypeReference<HashMap<String,MetadataPolicy>>() {};
- final EntityType type = new EntityType(entityType);
- try {
- if (entityStatement.getClaimsSet().getMetadataPolicy(type) != null) {
- return objectMapper.readValue(
- entityStatement.getClaimsSet().getMetadataPolicy(type).toJSONString(), typeRef);
+ final Object metadataPolicyClaim =
+ entityStatement.getClaimsSet().getClaim("metadata_policy");
+ if (metadataPolicyClaim != null) {
+ final JavaType stringType = objectMapper.constructType(String.class);
+ final JavaType metadataPolicyType = objectMapper.constructType(MetadataPolicy.class);
+ final MapType metadataPolicyMapType =
+ objectMapper.getTypeFactory().constructMapType(Map.class, stringType, metadataPolicyType);
+ final MapType metadataPolicyByEntityTypeMapType =
+ objectMapper.getTypeFactory().constructMapType(Map.class, stringType, metadataPolicyMapType);
+ try {
+ final Map<String, Map<String, MetadataPolicy>> result =
+ objectMapper.readValue(metadataPolicyClaim.toString(), metadataPolicyByEntityTypeMapType);
+ if (result != null) {
+ return result.get(entityType);
+ }
+ } catch (final JsonProcessingException e) {
+ log.warn("Could not parse trust mark issuers from the trust chain", e);
}
- } catch (final JsonProcessingException | PolicyViolationException e) {
- log.debug("Could not parse metadata policy of type {} from the claims set", entityType, e);
- throw new ConstraintViolationException("Could not parse metadata policy of type " + entityType +
- " from the claims set");
}
return null;
}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index add39a80..49a4c1ce 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -286,6 +286,41 @@
</property>
</bean>
+ <bean id="shibboleth.oidfed.policy.JSONObjectMapper" parent="shibboleth.oidc.JSONObjectMapper" />
+
+ <bean id="shibboleth.oidfed.policy.JSONSimpleModule" class="com.fasterxml.jackson.databind.module.SimpleModule"/>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ <property name="targetObject" ref="shibboleth.oidfed.policy.JSONSimpleModule" />
+ <property name="targetMethod" value="addDeserializer" />
+ <property name="arguments">
+ <list>
+ <value>#{ T(net.shibboleth.oidc.metadata.policy.MetadataPolicy)}</value>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.FederationMetadataPolicyDeserializer"/>
+ </list>
+ </property>
+ </bean>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+ <property name="targetObject" ref="shibboleth.oidfed.policy.JSONObjectMapper" />
+ <property name="targetMethod" value="registerModule" />
+ <property name="arguments">
+ <list>
+ <ref bean="shibboleth.oidfed.policy.JSONSimpleModule" />
+ </list>
+ </property>
+ </bean>
+
+ <util:list id="shibboleth.oidfed.StandardMetadataPolicyOperators">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyValueOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyAddOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyDefaultOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyOneOfOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicySubsetOfOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicySupersetOfOperator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyEssentialOperator"/>
+ </util:list>
+
<!-- Used in various places as a default for issuer. -->
<bean id="shibboleth.oidc.issuer" class="java.lang.String" c:_0="%{idp.oidc.issuer:%{idp.entityID}}" />
@@ -703,6 +738,18 @@
<!-- Missing from Nimbus. -->
<entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_TARGET}"
value="#{T(net.shibboleth.oidc.profile.core.OidcError).INVALID_TARGET}" />
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.INVALID_METADATA_POLICY"/>
+ </key>
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_metadata" c:_1="Merged metadata policy is invalid" c:_2="400" />
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.INVALID_METADATA_AGAINST_POLICY"/>
+ </key>
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_metadata" c:_1="Requested metadata is not compliant with the merged policy" c:_2="400" />
+ </entry>
</map>
</property>
</bean>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
index 29e7166a..b1755a2f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
@@ -91,11 +91,18 @@
p:preSelectedTrustChainIdsLookupStrategy="#{getObject('shibboleth.oidfed.PreSelectedTrustChainIDsLookupStrategy') ?: getObject('shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy')}"/>
<bean id="DefaultMetadataPolicyEnforcer"
- class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer" />
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyEnforcer"
+ p:metadataPolicyOperators-ref="#{'%{idp.oidfed.authorize.MetadataPolicyOperatorsy:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}"/>
<bean id="DefaultTrustChainMetadataPolicyMergingStrategy"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultTrustChainMetadataPolicyMergingStrategy"
- p:localMetadataPolicyStrategy-ref="#{'%{idp.oidfed.authorize.LocalMetadataPolicyStrategy:DefaultLocalMetadataPolicyStrategy}'.trim()}"/>
+ p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.authorize.MetadataPolicMergingyStrategy:MetadataPolicMergingyStrategy}'.trim()}"
+ p:localMetadataPolicyStrategy-ref="#{'%{idp.oidfed.authorize.LocalMetadataPolicyStrategy:DefaultLocalMetadataPolicyStrategy}'.trim()}"
+ p:objectMapper-ref="shibboleth.oidfed.policy.JSONObjectMapper"/>
+
+ <bean id="MetadataPolicMergingyStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyMergingStrategy"
+ p:metadataPolicyOperators-ref="#{'%{idp.oidfed.authorize.MetadataPolicyOperatorsy:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}"/>
<bean id="DefaultLocalMetadataPolicyStrategy"
parent="shibboleth.Functions.Constant">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
index 6ba028da..a977b87e 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
@@ -74,18 +74,25 @@
<bean id="ResolveTrustChains" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustChains"
scope="prototype"
- p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
+ p:trustChainCache-ref="#{'%{idp.oidfed.register.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
- p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
- p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"
+ p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.register.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
+ p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.register.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"
p:preSelectedTrustChainIdsLookupStrategy="#{getObject('shibboleth.oidfed.PreSelectedTrustChainIDsLookupStrategy') ?: getObject('shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy')}"/>
<bean id="DefaultMetadataPolicyEnforcer"
- class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer" />
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyEnforcer"
+ p:metadataPolicyOperators-ref="#{'%{idp.oidfed.register.MetadataPolicyOperatorsy:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}"/>
<bean id="DefaultTrustChainMetadataPolicyMergingStrategy"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultTrustChainMetadataPolicyMergingStrategy"
- p:localMetadataPolicyStrategy-ref="#{'%{idp.oidfed.authorize.LocalMetadataPolicyStrategy:DefaultLocalMetadataPolicyStrategy}'.trim()}"/>
+ p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.register.MetadataPolicMergingyStrategy:MetadataPolicMergingyStrategy}'.trim()}"
+ p:localMetadataPolicyStrategy-ref="#{'%{idp.oidfed.register.LocalMetadataPolicyStrategy:DefaultLocalMetadataPolicyStrategy}'.trim()}"
+ p:objectMapper-ref="shibboleth.oidfed.policy.JSONObjectMapper"/>
+
+ <bean id="MetadataPolicMergingyStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyMergingStrategy"
+ p:metadataPolicyOperators-ref="#{'%{idp.oidfed.register.MetadataPolicyOperatorsy:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}"/>
<bean id="DefaultLocalMetadataPolicyStrategy"
parent="shibboleth.Functions.Constant">
@@ -112,7 +119,7 @@
<bean id="ResolveTrustMarks" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustMarks"
scope="prototype"
- p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
+ p:trustChainCache-ref="#{'%{idp.oidfed.register.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
p:trustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.TrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}"
p:delegatedTrustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.DelegatedTrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}">
<property name="trustEngine">
@@ -162,7 +169,7 @@
scope="prototype">
<property name="localMetadataPolicyMergingStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultLocalMetadataPolicyMergingStrategy"
- p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"/>
+ p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.register.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"/>
</property>
<property name="mandatoryTrustMarksLookupStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.MandatoryTrustMarksLookupFunction"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
index ae78b670..74acd075 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
@@ -45,8 +45,8 @@
<bean id="shibboleth.oidfed.ResolveEntityResponseMetadataCacheBuilderSpec"
class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
- p:minCacheDuration="%{idp.oidfed.resolveEntity.maxRefreshDelay:PT1S}"
- p:maxCacheDuration="%{idp.oidfed.resolveEntity.maxRefreshDelay:PT30S}">
+ p:minCacheDuration="%{idp.oidfed.resolve-entity.maxRefreshDelay:PT1S}"
+ p:maxCacheDuration="%{idp.oidfed.resolve-entity.maxRefreshDelay:PT30S}">
<property name="criteriaToIdentifierStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultResolveEntityRequestCriteriaToIdentifierStrategy" />
</property>
@@ -75,18 +75,25 @@
<bean id="ResolveTrustChains" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustChains"
scope="prototype"
- p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
+ p:trustChainCache-ref="#{'%{idp.oidfed.resolve-entity.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
- p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
- p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"
+ p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.resolve-entity.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
+ p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.resolve-entity.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"
p:preSelectedTrustChainIdsLookupStrategy="#{getObject('shibboleth.oidfed.PreSelectedTrustChainIDsLookupStrategy') ?: getObject('shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy')}"/>
<bean id="DefaultMetadataPolicyEnforcer"
- class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer" />
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyEnforcer"
+ p:metadataPolicyOperators-ref="#{'%{idp.oidfed.resolve-entity.MetadataPolicyOperatorsy:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}"/>
<bean id="DefaultTrustChainMetadataPolicyMergingStrategy"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultTrustChainMetadataPolicyMergingStrategy"
- p:localMetadataPolicyStrategy-ref="#{'%{idp.oidfed.resolve.LocalMetadataPolicyStrategy:DefaultLocalMetadataPolicyStrategy}'.trim()}"/>
+ p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.resolve-entity.MetadataPolicMergingyStrategy:MetadataPolicMergingyStrategy}'.trim()}"
+ p:localMetadataPolicyStrategy-ref="#{'%{idp.oidfed.resolve-entity.LocalMetadataPolicyStrategy:DefaultLocalMetadataPolicyStrategy}'.trim()}"
+ p:objectMapper-ref="shibboleth.oidfed.policy.JSONObjectMapper"/>
+
+ <bean id="MetadataPolicMergingyStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyMergingStrategy"
+ p:metadataPolicyOperators-ref="#{'%{idp.oidfed.resolve-entity.MetadataPolicyOperatorsy:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}"/>
<bean id="DefaultLocalMetadataPolicyStrategy"
parent="shibboleth.Functions.Constant">
@@ -109,7 +116,7 @@
<bean id="ResolveTrustMarks" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustMarks"
scope="prototype"
- p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
+ p:trustChainCache-ref="#{'%{idp.oidfed.resolve-entity.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
p:trustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.TrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}"
p:delegatedTrustMarkClaimsValidationLookupStrategy="#{getObject('shibboleth.oidfed.DelegatedTrustMarkClaimsValidationLookupStrategy') ?: getObject('DefaultTrustMarkClaimsValidationLookupStrategy')}">
<property name="trustEngine">
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
index 6d35d051..af15caf3 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
@@ -213,7 +213,14 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
Assert.assertTrue(response instanceof ErrorResponse);
return (ErrorResponse) response;
}
-
+
+ protected ErrorResponse parseErrorResponse(final FlowExecutionResult result, final String message) {
+ final Response response = parseResponse(result);
+ Assert.assertFalse(response.indicatesSuccess(), message);
+ Assert.assertTrue(response instanceof ErrorResponse, message);
+ return (ErrorResponse) response;
+ }
+
protected <AResponseType extends Response> AResponseType parseSuccessResponse(final FlowExecutionResult result,
final Class<AResponseType> clazz) {
final Response response = parseResponse(result);
@@ -229,10 +236,21 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
Assert.assertEquals(errorResponse.getErrorObject().getCode(), errorCode);
}
+ protected void assertErrorCode(final FlowExecutionResult result, final String errorCode,
+ final String message) {
+ final ErrorResponse errorResponse = parseErrorResponse(result, message);
+ Assert.assertEquals(errorResponse.getErrorObject().getCode(), errorCode, message);
+ }
+
protected void assertErrorDescriptionContains(final FlowExecutionResult result, final String errorDescription) {
- final ErrorResponse errorResponse = parseErrorResponse(result);
- Assert.assertNotNull(errorResponse.getErrorObject().getDescription());
- Assert.assertTrue(errorResponse.getErrorObject().getDescription().contains(errorDescription));
+ assertErrorDescriptionContains(result, errorDescription, null);
+ }
+
+ protected void assertErrorDescriptionContains(final FlowExecutionResult result, final String errorDescription,
+ final String message) {
+ final ErrorResponse errorResponse = parseErrorResponse(result, message);
+ Assert.assertNotNull(errorResponse.getErrorObject().getDescription(), message);
+ Assert.assertTrue(errorResponse.getErrorObject().getDescription().contains(errorDescription), message);
}
protected void setJsonRequest(final String method, final String body) {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
index bba99f8d..35d197f5 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
@@ -23,11 +23,14 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.charset.Charset;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@@ -40,9 +43,14 @@ import org.apache.hc.core5.http.HttpEntity;
import org.mockito.ArgumentMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
@@ -54,9 +62,11 @@ import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+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;
@@ -68,8 +78,10 @@ import net.shibboleth.shared.logic.Constraint;
public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
final static AtomicInteger clientIndex = new AtomicInteger();
+ final static AtomicInteger intermediateIndex = new AtomicInteger();
final String redirectUri = "https://rp.federation.local/cb";
final String clientIdPattern = "https://testrp%s.federation.local";
+ final String intermediateIdPattern = "https://intermediate-authority%s.federation.local";
final String anchorId = "https://trust-anchor.federation.local";
final String anchorFetchEndpoint = anchorId + "/fetch";
String issuer = "https://op.example.org";
@@ -78,6 +90,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
JWK leafKey;
JWK anchorKey;
JWK trustedAnchorKey;
+ JWK intermediateKey;
@Autowired
@Qualifier("shibboleth.oidfed.HttpClient")
@@ -97,6 +110,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
.privateKey(localAnchor.getPrivateKey())
.keyID("locallyTrustedAnchorKey")
.build();
+ intermediateKey = initializeNewJwk("RSA", 2048, "mockIntermediateKey");
}
protected JWK initializeNewJwk(final String algorithm, final int size, final String kid)
@@ -170,21 +184,23 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
return classicResponse;
}
- protected String rpEntityConfiguration(final String clientId) throws URISyntaxException {
+ protected String rpEntityConfiguration(final String clientId, final String... authorityHints)
+ throws URISyntaxException {
final OIDCClientMetadata metadata = new OIDCClientMetadata();
metadata.setRedirectionURI(new URI(redirectUri));
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
- return rpEntityConfiguration(clientId, metadata);
+ return rpEntityConfiguration(clientId, metadata, authorityHints);
}
- protected String rpEntityConfiguration(final String clientId, final OIDCClientMetadata metadata)
- throws URISyntaxException {
+ protected String rpEntityConfiguration(final String clientId, final OIDCClientMetadata metadata,
+ final String... authorityHints) throws URISyntaxException {
final JWTClaimsSet claimsSet = 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", new String[] { anchorId })
+ .claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
+ new String[] { anchorId } : authorityHints)
.build();
final EntityStatement rpConfiguration =
TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, claimsSet);
@@ -205,6 +221,20 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
return anchorConfiguration.getSignedStatement().serialize();
}
+ protected String intermediateConfiguration(final String intermediateId) {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(intermediateId).subject(intermediateId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(intermediateKey).toJSONObject(true))
+ .claim("metadata", Map.of("federation_entity", Map.of("federation_fetch_endpoint",
+ intermediateId + "/fetch")))
+ .claim("authority_hints", new String[] { anchorId })
+ .build();
+ final EntityStatement anchorConfiguration =
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, intermediateKey, claimsSet);
+ return anchorConfiguration.getSignedStatement().serialize();
+ }
+
protected String subordinateStatement(final String clientId) {
final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(anchorId).subject(clientId)
.issueTime(Date.from(Instant.now()))
@@ -217,11 +247,29 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustedAnchorKey, claimsSet);
return rpConfiguration.getSignedStatement().serialize();
}
-
+
+ protected String subordinateStatement(final String issuer, final JWK issuerKey, final JWK subjetKey,
+ final String subjectId, final Map<String, Object> rpPolicy, final String... authorityHints) {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(issuer).subject(subjectId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(subjetKey).toJSONObject(true))
+ .claim("metadata", Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ .claim("metadata_policy", Map.of("openid_relying_party", rpPolicy))
+ .claim("authority_hints", authorityHints)
+ .build();
+ final EntityStatement rpConfiguration =
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, issuerKey, claimsSet);
+ return rpConfiguration.getSignedStatement().serialize();
+ }
protected String uniqueClientId() {
return String.format(clientIdPattern, clientIndex.getAndIncrement());
}
+ protected String uniqueIntermediateId() {
+ return String.format(intermediateIdPattern, intermediateIndex.getAndIncrement());
+ }
+
protected void configureMockHttpClient(final String clientId) {
try {
mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration(clientId)));
@@ -244,6 +292,53 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
}
}
+ @SuppressWarnings("unchecked")
+ protected void configureMockHttpClient(final String clientId, final Map<String, Object> testVector) {
+ final String intermediateId = uniqueIntermediateId();
+ try {
+ final Map<String, Object> vectorMetadata = (Map<String, Object>) testVector.get("metadata");
+ if (vectorMetadata.isEmpty()) {
+ mapResponse(entityConfigurationUrl(clientId), mockResponse(
+ rpEntityConfiguration(clientId, intermediateId)));
+ } else {
+ final Map<String, Object> metadata = new HashMap<>(vectorMetadata);
+ metadata.put("redirect_uris", List.of(redirectUri));
+ metadata.put("jwks", new JWKSet(rpKey.toPublicJWK()).toJSONObject());
+ mapResponse(entityConfigurationUrl(clientId),
+ mockResponse(rpEntityConfiguration(clientId,
+ OIDCClientMetadata.parse(new JSONObject(metadata)), intermediateId)));
+ }
+ mapResponse(entityConfigurationUrl(intermediateId), mockResponse(intermediateConfiguration(intermediateId)));
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, intermediateId),
+ mockResponse(subordinateStatement(anchorId, trustedAnchorKey, intermediateKey, intermediateId,
+ (Map<String, Object>) testVector.get("TA"), anchorId)));
+ mapResponse(subordinateStatementUrl(intermediateId + "/fetch", clientId),
+ mockResponse(subordinateStatement(intermediateId, intermediateKey, rpKey, clientId,
+ (Map<String, Object>) testVector.get("INT"), intermediateId)));
+ } catch (UnsupportedOperationException | IOException | URISyntaxException | ParseException e) {
+ Assert.fail("Could not initialize mock HTTP client", e);
+ }
+ }
+
+ protected List<Map<String, Object>> loadPolicyTestVectors() throws IOException {
+ final ObjectMapper objectMapper = new ObjectMapper();
+ final Resource file = new ClassPathResource(
+ "/net/shibboleth/idp/oidc/metadata/impl/metadata-policy-test-vectors-2025-02-13.json");
+
+ final Charset utf8 = Charset.forName("UTF-8");
+ assert utf8 != null;
+ try {
+ final List<Map<String, Object>> policies =
+ objectMapper.readValue(file.getContentAsString(utf8),
+ new TypeReference<List<Map<String, Object>>>(){});
+ Assert.assertNotNull(policies);
+ return policies;
+ } catch (final JsonProcessingException e) {
+ throw new IOException("Could not parse JSON from the input", e);
+ }
+ }
+
protected class RequestUriMatcher implements ArgumentMatcher<ClassicHttpRequest> {
@Nonnull private final String uri;
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
index 6747f192..a210a44a 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
@@ -314,6 +314,33 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
assertErrorCode(result, OAuth2Error.INVALID_CLIENT_CODE);
}
+ @Test
+ public void testWithTestVectors() throws Exception {
+ final List<Map<String, Object>> vectors = loadPolicyTestVectors();
+ for (final Map<String, Object> vector : vectors) {
+ initializeMocks();
+ initializeThreadLocals();
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId, vector);
+ final SignedJWT jwt = createPrivateKeyJWT(validClaimsSet(clientId, issuer),
+ rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS256);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.PRIVATE_KEY_JWT, rpKey.toRSAKey().toPublicKey());
+ final String ERROR_MESSAGE = "Unexpected result with test vector " + vector.get("n");
+ if (vector.get("error") instanceof String error) {
+ assertErrorCode(result, "invalid_metadata", ERROR_MESSAGE);
+ if ("invalid_policy".equals(error)) {
+ assertErrorDescriptionContains(result, "Merged metadata policy is invalid", ERROR_MESSAGE);
+ } else {
+ assertErrorDescriptionContains(result, "Requested metadata is not compliant with the merged policy",
+ ERROR_MESSAGE);
+ }
+ } else {
+ assertSuccessResponse(result, clientId, ERROR_MESSAGE);
+ }
+ }
+ }
+
protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri) {
verifyAuthorizeEndpoint(clientId, requestUri, null);
}
@@ -408,11 +435,15 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
}
protected void assertSuccessResponse(final FlowExecutionResult result, final String id) {
+ assertSuccessResponse(result, id, null);
+ }
+
+ protected void assertSuccessResponse(final FlowExecutionResult result, final String id, final String message) {
final PushedAuthorizationSuccessResponse resp =
parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
- Assert.assertNotNull(resp);
- Assert.assertNotNull(resp.getRequestURI());
- Assert.assertNotNull(resp.getLifetime());
+ Assert.assertNotNull(resp, message);
+ Assert.assertNotNull(resp.getRequestURI(), message);
+ Assert.assertNotNull(resp.getLifetime(), message);
}
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list