[java-idp-oidc] branch dev/JOIDC-222 updated: JOIDC-222 - Support for OpenID Federation
Henri Mikkonen
henri.mikkonen at iki.fi
Wed Aug 6 06:57:57 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=9e894a359c61b0b911485d1c2072d888c0d80f38
The following commit(s) were added to refs/heads/dev/JOIDC-222 by this push:
new 9e894a35 JOIDC-222 - Support for OpenID Federation
9e894a35 is described below
commit 9e894a359c61b0b911485d1c2072d888c0d80f38
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Aug 6 09:57:29 2025 +0300
JOIDC-222 - Support for OpenID Federation
https://shibboleth.atlassian.net/browse/JOIDC-222
Generalized trust chain resolution from pure RP resolution
- Metadata claim is parsed into Map<String,Map<String,Object>> via ObjectMapper
- Metadata syntax validation is done via parsing to corresponding Nimbus object
- Can be customized via idp.oidfed.MetadataValidationCondition (BiPredicate<String,Map<String,Object>>)
Improved Resolve Entity API's support for the entity_type -parameter
- Not hardcoded to openid_relying_party anymore
- The use of the parameter is optional
---
.../decoding/impl/ResolveEntityRequestDecoder.java | 12 +-
.../messaging/impl/ResolveEntityRequest.java | 22 +--
...ClientMetadataFromTrustChainLookupStrategy.java | 78 -----------
...mbinedMetadataFromTrustChainLookupStrategy.java | 109 ++++++++++++++
.../op/oidfed/metadata/EntityStatementHelper.java | 41 +++++-
.../impl/DefaultAllowedEntityTypesConstraint.java | 2 +-
.../impl/AbstractTrustChainResolutionAction.java | 105 ++++++++------
.../profile/impl/BuildResolveEntityResponse.java | 54 +++++--
.../oidfed/profile/impl/OidFederationEventIds.java | 9 +-
.../impl/RelyingPartyTrustChainContext.java | 17 ++-
.../op/oidfed/profile/impl/ResolveTrustChains.java | 9 +-
.../op/oidfed/profile/impl/ResolveTrustMarks.java | 3 +-
.../op/oidfed/profile/impl/SelectTrustChain.java | 9 +-
.../profile/impl/StoreAutomaticRegistration.java | 48 ++++++-
...eAutomaticRegistrationProfileConfiguration.java | 45 ++++--
.../profile/impl/ValidateProvidedTrustChain.java | 5 +-
.../profile/impl/ValidateResolveEntityRequest.java | 4 +-
.../profile/impl/ValidateSelectedTrustChain.java | 6 +-
.../navigate/DefaultEntityTypesLookupFunction.java | 46 ++++++
.../DefaultMetadataValidationCondition.java | 81 +++++++++++
...ltSelectedTrustChainMetadataLookupStrategy.java | 15 +-
.../DefaultTrustChainSelectionStrategy.java | 8 +-
.../META-INF/net.shibboleth.idp/postconfig.xml | 12 ++
.../oidc/metadata-lookup/metadata-lookup-beans.xml | 11 +-
.../idp/flows/oidfed/register/register-beans.xml | 15 +-
.../oidfed/resolve-entity/resolve-entity-beans.xml | 11 +-
.../flow/oidfed/AbstractFederationFlowTest.java | 156 ++++++++++++++++++---
.../AuthorizeFlowAutomaticRegistrationTest.java | 38 ++---
...shedAuthorizeFlowAutomaticRegistrationTest.java | 28 ++--
.../profile/flow/oidfed/RegistrationFlowTest.java | 24 ++--
.../profile/flow/oidfed/ResolveEntityFlowTest.java | 46 +++++-
31 files changed, 789 insertions(+), 280 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ResolveEntityRequestDecoder.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ResolveEntityRequestDecoder.java
index c83878e9..655bccc1 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ResolveEntityRequestDecoder.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ResolveEntityRequestDecoder.java
@@ -78,15 +78,7 @@ public class ResolveEntityRequestDecoder extends BaseOAuth2RequestDecoder<Resolv
if (trustAnchors == null) {
throw new MessageDecodingException("No trust_anchor included in the request");
}
- final String entityType = Optional.ofNullable(parameters.get("entity_type"))
- .filter(Objects::nonNull)
- .filter(list -> list.size() == 1)
- .map(list -> list.get(0))
- .orElse(null);
- if (entityType == null) {
- throw new MessageDecodingException("No single entity_type value in the request");
- }
- return new ResolveEntityRequest(uri, subject, trustAnchors, entityType);
+ return new ResolveEntityRequest(uri, subject, trustAnchors, parameters.get("entity_type"));
} catch (final IOException e) {
log.error("Could not create HTTP request from the request", e);
throw new MessageDecodingException(e);
@@ -99,7 +91,7 @@ public class ResolveEntityRequestDecoder extends BaseOAuth2RequestDecoder<Resolv
return message == null ? null : MoreObjects.toStringHelper(this).omitNullValues()
.add("subject", message.getSubject())
.add("trustAnchors", message.getTrustAnchors())
- .add("entityType", message.getEntityType())
+ .add("entityTypes", message.getEntityTypes())
.add("endpointURI", getEndpointURI(message))
.toString();
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/ResolveEntityRequest.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/ResolveEntityRequest.java
index 79d59f05..2c00b0f9 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/ResolveEntityRequest.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/ResolveEntityRequest.java
@@ -18,12 +18,14 @@ import java.net.URI;
import java.util.List;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import com.google.common.base.MoreObjects;
import com.nimbusds.oauth2.sdk.Request;
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.logic.Constraint;
/**
@@ -40,8 +42,8 @@ public class ResolveEntityRequest implements Request {
/** The requested trust anchors. */
@Nonnull @NotEmpty private final List<String> trustAnchors;
- /** The requested entity type to resolve. */
- @Nonnull @NotEmpty private final String entityType;
+ /** The requested entity types to resolve. */
+ @Nonnull private final List<String> entityTypes;
/**
*
@@ -50,17 +52,17 @@ public class ResolveEntityRequest implements Request {
* @param uri endpoint URI
* @param sub subject
* @param anchors trust anchors
- * @param type entity type
+ * @param types optional entity types
*/
public ResolveEntityRequest(@Nonnull final URI uri,
@Nonnull @NotEmpty final String sub,
@Nonnull @NotEmpty final List<String> anchors,
- @Nonnull @NotEmpty final String type) {
+ @Nullable final List<String> types) {
endpointUri = Constraint.isNotNull(uri, "Endpoint URI cannot be null");
subject = Constraint.isNotNull(sub, "Subject cannot be empty");
Constraint.isNotEmpty(anchors, "Trust anchors cannot be empty");
trustAnchors = anchors;
- entityType = Constraint.isNotNull(type, "Entity type cannot be empty");
+ entityTypes = types == null ? CollectionSupport.emptyList() : CollectionSupport.copyToList(types);
}
/**
@@ -86,8 +88,8 @@ public class ResolveEntityRequest implements Request {
*
* @return The entity type to resolve.
*/
- @Nonnull @NotEmpty public String getEntityType() {
- return entityType;
+ @Nonnull public List<String> getEntityTypes() {
+ return entityTypes;
}
/** {@inheritDoc} */
@@ -108,7 +110,7 @@ public class ResolveEntityRequest implements Request {
return MoreObjects.toStringHelper(this)
.add("subject", getSubject())
.add("trustAnchors", getTrustAnchors())
- .add("entityType", getEntityType())
+ .add("entityTypes", getEntityTypes())
.add("endpointURI", getEndpointURI())
.toString();
}
@@ -127,7 +129,7 @@ public class ResolveEntityRequest implements Request {
}
final ResolveEntityRequest other = (ResolveEntityRequest) obj;
return endpointUri.equals(other.endpointUri) && subject.equals(other.subject) &&
- entityType.equals(other.entityType) && trustAnchors.containsAll(other.trustAnchors) &&
- other.trustAnchors.containsAll(trustAnchors);
+ entityTypes.containsAll(other.entityTypes) && other.entityTypes.containsAll(entityTypes) &&
+ trustAnchors.containsAll(other.trustAnchors) && other.trustAnchors.containsAll(trustAnchors);
}
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultClientMetadataFromTrustChainLookupStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultClientMetadataFromTrustChainLookupStrategy.java
deleted file mode 100644
index f9893472..00000000
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultClientMetadataFromTrustChainLookupStrategy.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.metadata;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.slf4j.Logger;
-
-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.shared.primitive.LoggerFactory;
-
-/**
- * Default strategy to combine {@link OIDCClientMetadata} from the trust chain by exploiting both entity configuration
- * and subordinate statement issued by the immediate superior.
- */
-public class DefaultClientMetadataFromTrustChainLookupStrategy
- implements Function<List<EntityStatement>,OIDCClientMetadata> {
-
- /** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(DefaultClientMetadataFromTrustChainLookupStrategy.class);
-
- /** {@inheritDoc} */
- @Override @Nullable
- public OIDCClientMetadata apply(@Nullable final List<EntityStatement> chain) {
- if (chain == null || chain.size() < 3) {
- log.warn("Unexpected trust chain input: {}", chain == null ? null : "size = " + chain.size());
- return null;
- }
- final OIDCClientMetadata configurationMetadata = chain.get(0).getClaimsSet().getRPMetadata();
- final OIDCClientMetadata subordinateMetadata = chain.get(1).getClaimsSet().getRPMetadata();
- if (subordinateMetadata == null || subordinateMetadata.toJSONObject().isEmpty()) {
- log.trace("Subordinate statement metadata is empty, using entity configuration");
- return configurationMetadata;
- }
- if (configurationMetadata == null || configurationMetadata.toJSONObject().isEmpty()) {
- log.trace("Entity configuration metadata is empty, using subordinate statement");
- return subordinateMetadata;
- }
- final Map<String, Object> configurationClaims = configurationMetadata.toJSONObject();
- final Map<String, Object> subordinateClaims = subordinateMetadata.toJSONObject();
- for (final String configurationClaim : configurationClaims.keySet()) {
- if (!subordinateClaims.containsKey(configurationClaim)) {
- log.trace("Including metadata claim {} from the entity configuration", configurationClaim);
- subordinateClaims.put(configurationClaim, configurationClaims.get(configurationClaim));
- } else {
- log.trace("Keeping metadata claim {} from the subordinate configuration", configurationClaim);
- }
- }
- try {
- return OIDCClientMetadata.parse(new JSONObject(subordinateClaims));
- } catch (final ParseException e) {
- log.error("Could not parse from combined metadata map into a metadata object", e);
- }
- return null;
- }
-
-}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultCombinedMetadataFromTrustChainLookupStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultCombinedMetadataFromTrustChainLookupStrategy.java
new file mode 100644
index 00000000..ec2eda58
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultCombinedMetadataFromTrustChainLookupStrategy.java
@@ -0,0 +1,109 @@
+/*
+ * 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.metadata;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default strategy to combine metadata claim contents from the trust chain by exploiting both entity configuration
+ * and subordinate statement issued by the immediate superior.
+ */
+public class DefaultCombinedMetadataFromTrustChainLookupStrategy extends AbstractIdentifiableInitializableComponent
+ implements Function<List<EntityStatement>,Map<String,Map<String,Object>>> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultCombinedMetadataFromTrustChainLookupStrategy.class);
+
+ /** Object mapper used for deserializing metadata. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+ /**
+ * Set the object mapper used for deserializing metadata
+ *
+ * @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 (objectMapper == null) {
+ throw new ComponentInitializationException("Object mapper cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public Map<String,Map<String,Object>> apply(@Nullable final List<EntityStatement> chain) {
+ if (chain == null || chain.size() < 3) {
+ log.warn("Unexpected trust chain input: {}", chain == null ? null : "size = " + chain.size());
+ return null;
+ }
+ final Map<String,Map<String,Object>> configurationMetadata =
+ EntityStatementHelper.parseMetadata(objectMapper, chain.get(0));
+ final Map<String,Map<String,Object>> subordinateMetadata =
+ EntityStatementHelper.parseMetadata(objectMapper, chain.get(1));
+
+ if (configurationMetadata == null || configurationMetadata.isEmpty()) {
+ log.error("Entity configuration for {} doesn't contain metadata", chain.get(0).getEntityID().getValue());
+ return null;
+ }
+
+ final Map<String,Map<String,Object>> result = new HashMap<>();
+ for (final String entityType : configurationMetadata.keySet()) {
+ final Map<String, Object> configurationClaims = configurationMetadata.get(entityType);
+ final Map<String, Object> subordinateClaims = new HashMap<>(Optional.ofNullable(subordinateMetadata)
+ .map(metadata -> metadata.get(entityType))
+ .orElse(CollectionSupport.emptyMap()));
+
+ for (final String configurationClaim : configurationClaims.keySet()) {
+ if (!subordinateClaims.containsKey(configurationClaim)) {
+ log.trace("Including metadata claim {} from the entity configuration", configurationClaim);
+ subordinateClaims.put(configurationClaim, configurationClaims.get(configurationClaim));
+ } else {
+ log.trace("Keeping metadata claim {} from the subordinate configuration", configurationClaim);
+ }
+ }
+
+ result.put(entityType, subordinateClaims);
+ }
+
+ return result;
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatementHelper.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatementHelper.java
index de05c5f6..fc2491cf 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatementHelper.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/EntityStatementHelper.java
@@ -62,7 +62,42 @@ public class EntityStatementHelper {
return result;
}
} catch (final JsonProcessingException e) {
- log.warn("Could not parse " + claim + " from the subordinate statement", e);
+ log.warn("Could not parse " + claim + " from entity statement", e);
+ }
+ }
+ log.trace("Returning empty map");
+ return CollectionSupport.emptyMap();
+ }
+
+ /**
+ * Parses the given claim as Map of Map of Objects from the given entity statement.
+ *
+ * @param objectMapper object mapper used for parsing
+ * @param entityStatement entity statement from which to parse the claim from
+ * @param claim claim name to be parsed
+ * @return claim value as map or empty map if it didn't exist or could be parsed
+ */
+ @Nonnull public static Map<String, Map<String, Object>> parseClaimAsMapOfMaps(
+ @Nonnull final ObjectMapper objectMapper, @Nonnull final EntityStatement entityStatement,
+ @Nonnull final String claim ) {
+ final Object rawClaim = entityStatement.getClaimsSet().getClaim(claim);
+ log.trace("Raw {} claim value: {}", claim, rawClaim);
+ if (rawClaim != null) {
+ final JavaType objectType = objectMapper.constructType(Object.class);
+ final JavaType stringType = objectMapper.constructType(String.class);
+ final MapType objectMapType =
+ objectMapper.getTypeFactory().constructMapType(Map.class, stringType, objectType);
+ final MapType mapOfObjectMapType =
+ objectMapper.getTypeFactory().constructMapType(Map.class, stringType, objectMapType);
+ try {
+ final Map<String, Map<String, Object>> result =
+ objectMapper.readValue(rawClaim.toString(), mapOfObjectMapType);
+ if (result != null) {
+ log.trace("Parsed {} map: {}", claim, result);
+ return result;
+ }
+ } catch (final JsonProcessingException e) {
+ log.warn("Could not parse " + claim + " from entity statement", e);
}
}
log.trace("Returning empty map");
@@ -76,8 +111,8 @@ public class EntityStatementHelper {
* @param entityStatement entity statement from which to parse the claim from
* @return the map of metadata or empty map if they didn't exist or could be parsed
*/
- @Nonnull public static Map<String, Object> parseMetadata(@Nonnull final ObjectMapper objectMapper,
+ @Nonnull public static Map<String, Map<String, Object>> parseMetadata(@Nonnull final ObjectMapper objectMapper,
@Nonnull final EntityStatement entityStatement) {
- return parseClaimAsMap(objectMapper, entityStatement, "metadata");
+ return parseClaimAsMapOfMaps(objectMapper, entityStatement, "metadata");
}
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
index f90c125b..f18fe3e7 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
@@ -90,7 +90,7 @@ public class DefaultAllowedEntityTypesConstraint extends AbstractFederationPolic
for (final EntityStatement entityStatement : trustChain) {
assert entityStatement != null;
assert objectMapper != null;
- final Map<String, Object> metadata =
+ final Map<String, Map<String,Object>> metadata =
EntityStatementHelper.parseMetadata(objectMapper, entityStatement);
for (final String entityType : metadata.keySet()) {
if (!allowedTypes.contains(entityType)) {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java
index 59e75142..12288d7b 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTrustChainResolutionAction.java
@@ -14,10 +14,12 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -28,15 +30,9 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.slf4j.Logger;
-import com.nimbusds.oauth2.sdk.ParseException;
-import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import com.nimbusds.openid.connect.sdk.federation.entities.EntityType;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
import net.minidev.json.JSONObject;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultClientMetadataFromTrustChainLookupStrategy;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.FederationMetadataPolicyHelper;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
@@ -67,8 +63,8 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
/** Strategy used to create the trust chain context. */
@Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextCreationStrategy;
- /** Strategy used to get combined OIDC client metadata from trust chain. */
- @Nonnull private Function<List<EntityStatement>,OIDCClientMetadata> metadataLookupStrategy;
+ /** Strategy used to get combined entity metadata from trust chain. */
+ @Nonnull private Function<List<EntityStatement>,Map<String,Map<String,Object>>> metadataLookupStrategy;
/** Strategy used to merge metadata policies in trust chain for specific entity type. */
@NonnullAfterInit private BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>>
@@ -77,6 +73,9 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
/** Enforcer function for applying metadata policy for an item. */
@NonnullAfterInit private BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> metadataPolicyEnforcer;
+ /** Condition used to validate metadata for an entity type. */
+ @NonnullAfterInit private BiPredicate<String, Map<String, Object>> metadataValidationCondition;
+
/** List of claim names who are transformed from a space-separated String into a List. */
@Nonnull private List<String> arraysAsSpaceSeparatedList;
@@ -89,7 +88,6 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
new InboundMessageContextLookup());
assert tccs != null;
trustChainContextCreationStrategy = tccs;
- metadataLookupStrategy = new DefaultClientMetadataFromTrustChainLookupStrategy();
arraysAsSpaceSeparatedList = CollectionSupport.listOf("scope");
}
@@ -117,22 +115,23 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
}
/**
- * Set the strategy used to get combined OIDC client metadata from trust chain.
+ * Set the strategy used to get combined entity metadata from trust chain.
*
* @param strategy lookup strategy
*/
- public void setMetadataLookupStrategy(@Nonnull final Function<List<EntityStatement>,OIDCClientMetadata> strategy) {
+ public void setMetadataLookupStrategy(
+ @Nonnull final Function<List<EntityStatement>,Map<String,Map<String,Object>>> strategy) {
checkSetterPreconditions();
metadataLookupStrategy =
Constraint.isNotNull(strategy, "MetadataLookupStrategy cannot be null");
}
/**
- * Get the strategy used to get combined OIDC client metadata from trust chain.
+ * Get the strategy used to get combined entity metadata from trust chain.
*
* @return lookup strategy
*/
- @Nonnull public Function<List<EntityStatement>,OIDCClientMetadata> getMetadataLookupStrategy() {
+ @Nonnull public Function<List<EntityStatement>,Map<String,Map<String,Object>>> getMetadataLookupStrategy() {
checkComponentActive();
return metadataLookupStrategy;
}
@@ -184,7 +183,7 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
}
/**
- * Set the list of claim names who are transformed from a space-separated String into a List.
+ * Set the list of claim names who are transformed from a space-separated String into a List.
*
* @param list list of claim names
*/
@@ -194,7 +193,7 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
}
/**
- * Get the list of claim names who are transformed from a space-separated String into a List.
+ * Get the list of claim names who are transformed from a space-separated String into a List.
*
* @return list of claim names
*/
@@ -203,6 +202,16 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
return arraysAsSpaceSeparatedList;
}
+ /**
+ * Set the condition used to validate metadata for an entity type.
+ *
+ * @param condition validation condition
+ */
+ public void setMetadataValidationCondition(@Nonnull final BiPredicate<String,Map<String,Object>> condition) {
+ checkSetterPreconditions();
+ metadataValidationCondition = Constraint.isNotNull(condition, "MetadataValidationCondition cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
@@ -214,6 +223,9 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
if (metadataPolicyEnforcer == null) {
throw new ComponentInitializationException("MetadataPolicyEnforcer cannot be null");
}
+ if (metadataValidationCondition == null) {
+ throw new ComponentInitializationException("MetadataValidationCondition cannot be null");
+ }
}
/**
@@ -224,43 +236,46 @@ public class AbstractTrustChainResolutionAction extends AbstractProfileAction {
* @return error event ID if metadata policy merging or enforcement failed, null otherwise
*/
@Nullable protected String populatePolicyComplaintChains(@Nonnull final List<EntityStatement> chain,
- @Nonnull List<Pair<List<EntityStatement>, OIDCClientInformation>> policyCompliantChains) {
- final Map<String, MetadataPolicy> mergedPolicies;
- try {
- mergedPolicies = getMetadataPolicyMergingStrategy().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);
- return OidFederationEventIds.INVALID_METADATA_POLICY;
- }
- assert chain != null;
- final OIDCClientMetadata metadata = getMetadataLookupStrategy().apply(chain);
+ @Nonnull List<Pair<List<EntityStatement>, Map<String,Map<String,Object>>>> policyCompliantChains) {
+ final Map<String,Map<String,Object>> metadata = getMetadataLookupStrategy().apply(chain);
log.trace("{} Metadata resolved via lookup strategy: {}", getLogPrefix(), metadata);
if (metadata != null) {
- final OIDCClientInformation clientInformation = new OIDCClientInformation(
- new ClientID(chain.get(0).getEntityID().getValue()), metadata);
- final JSONObject requestMetadata = clientInformation.toJSONObject();
- for (final String claim : mergedPolicies.keySet()) {
- assert claim != null;
- final MetadataPolicy policy = mergedPolicies.get(claim);
+ final Map<String,Map<String,Object>> verifiedMetadata = new HashMap<>();
+ for (final String entityType : metadata.keySet()) {
+ final Map<String, MetadataPolicy> mergedPolicies;
try {
- final Object enforcedValue = enforceValue(claim, requestMetadata.get(claim), policy);
- requestMetadata.put(claim, enforcedValue);
+ mergedPolicies = getMetadataPolicyMergingStrategy().apply(chain, entityType);
+ log.debug("{} Merged policy for type {} for chain {}", getLogPrefix(), entityType, mergedPolicies);
} catch (final ConstraintViolationException e) {
- log.warn("{} The requested metadata is not compliant with the policy", getLogPrefix());
- return OidFederationEventIds.INVALID_METADATA_AGAINST_POLICY;
+ log.warn("{} Could not merge metadata policies", getLogPrefix(), e);
+ return OidFederationEventIds.INVALID_METADATA_POLICY;
}
- }
- log.debug("{} The requested metadata is compliant with the policy", getLogPrefix());
- try {
- policyCompliantChains.add(new Pair<>(chain, OIDCClientInformation.parse(requestMetadata)));
- log.debug("{} Policy-enforced metadata {}", getLogPrefix(), requestMetadata.toJSONString());
- return null;
- } catch (final ParseException e) {
- log.error("{} Could not parse the metadata object", getLogPrefix(), e);
+ final JSONObject requestMetadata = new JSONObject(metadata.get(entityType));
+ for (final String claim : mergedPolicies.keySet()) {
+ assert claim != null;
+ final MetadataPolicy policy = mergedPolicies.get(claim);
+ try {
+ final Object enforcedValue = enforceValue(claim, requestMetadata.get(claim), policy);
+ requestMetadata.put(claim, enforcedValue);
+ } catch (final ConstraintViolationException e) {
+ log.warn("{} The requested metadata is not compliant with the policy", getLogPrefix());
+ return OidFederationEventIds.INVALID_METADATA_AGAINST_POLICY;
+ }
+ }
+
+ log.debug("{} The requested metadata is compliant with the policy", getLogPrefix());
+ if (metadataValidationCondition.test(entityType, requestMetadata)) {
+ verifiedMetadata.put(entityType, requestMetadata);
+ log.debug("{} Policy-enforced metadata {}", getLogPrefix(), requestMetadata.toJSONString());
+ } else {
+ log.warn("{} Metadata validation failed for {} for entity type {}", getLogPrefix(),
+ chain.get(0).getEntityID(), entityType);
+ }
}
+ policyCompliantChains.add(new Pair<>(chain, verifiedMetadata));
+ log.debug("{} Policy-enforced metadata {}", getLogPrefix(), verifiedMetadata);
+ return null;
}
return EventIds.INVALID_MSG_CTX;
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityResponse.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityResponse.java
index c5714b20..a17184aa 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityResponse.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityResponse.java
@@ -37,10 +37,9 @@ import org.slf4j.Logger;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultEntityTypesLookupFunction;
import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.collection.Pair;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
@@ -57,6 +56,9 @@ public class BuildResolveEntityResponse extends AbstractBuildEntityStatementActi
/** Strategy used to lookup the trust chain context. */
@Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+ /** Strategy used to lookup the entity types included in the response metadata. */
+ @Nonnull private Function<ProfileRequestContext, List<String>> entityTypesLookupStrategy;
+
/** Trust chain context to operate on. */
@NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
@@ -67,6 +69,7 @@ public class BuildResolveEntityResponse extends AbstractBuildEntityStatementActi
new InboundMessageContextLookup());
assert tcls != null;
trustChainContextLookupStrategy = tcls;
+ entityTypesLookupStrategy = new DefaultEntityTypesLookupFunction();
}
/**
@@ -81,6 +84,16 @@ public class BuildResolveEntityResponse extends AbstractBuildEntityStatementActi
Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
}
+ /**
+ * Set the strategy used to lookup the entity types included in the response metadata.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityTypesLookupStrategy(@Nonnull final Function<ProfileRequestContext, List<String>> strategy) {
+ checkSetterPreconditions();
+ entityTypesLookupStrategy = Constraint.isNotNull(strategy, "EntityTypesLookupStrategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -102,27 +115,42 @@ public class BuildResolveEntityResponse extends AbstractBuildEntityStatementActi
@Override
protected boolean populateClaimsSetBuilder(@Nonnull final JWTClaimsSet.Builder builder,
@Nonnull final ProfileRequestContext profileRequestContext) {
- final Pair<List<EntityStatement>,OIDCClientInformation> selectedTrustChain =
+ final Pair<List<EntityStatement>,Map<String,Map<String,Object>>> selectedTrustChain =
trustChainContext.getSelectedTrustChain();
if (selectedTrustChain == null) {
log.debug("{} No selected trust chain found form the context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.INVALID_METADATA);
return false;
}
- final OIDCClientInformation clientInformation = selectedTrustChain.getSecond();
- if (clientInformation == null) {
- log.debug("{} No client information set in the trust chain context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ final Map<String,Map<String,Object>> metadata = selectedTrustChain.getSecond();
+ if (metadata == null) {
+ log.debug("{} No metadata set for the selected trust chain", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.INVALID_METADATA);
return false;
}
- builder.claim("metadata",
- CollectionSupport.singletonMap("openid_relying_party", clientInformation.toJSONObject()));
+ final List<String> entityTypes = entityTypesLookupStrategy.apply(profileRequestContext);
+ log.trace("{} The following entity types were requested: {}", getLogPrefix(), entityTypes);
+ if (entityTypes != null && !entityTypes.isEmpty()) {
+ final Map<String,Object> filteredMetadata = metadata.entrySet()
+ .stream()
+ .filter(entry -> entityTypes.contains(entry.getKey()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ if (filteredMetadata.isEmpty()) {
+ log.warn("{} No metadata for entity types {} found for the selected trust chain", getLogPrefix(),
+ entityTypes);
+ ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.INVALID_METADATA);
+ return false;
+ }
+ builder.claim("metadata", filteredMetadata);
+ } else {
+ builder.claim("metadata", metadata);
+ }
final List<EntityStatement> trustChain = selectedTrustChain.getFirst();
if (trustChain == null || trustChain.isEmpty()) {
log.debug("{} No selected trust chain set in the trust chain context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
+ ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.INVALID_METADATA);
+ return false;
}
builder.claim("trust_chain", trustChain.stream()
.map(statement -> statement.getSignedStatement().serialize())
@@ -136,7 +164,7 @@ public class BuildResolveEntityResponse extends AbstractBuildEntityStatementActi
}
builder.expirationTime(Date.from(expirationTime));
- final String entityId = clientInformation.getID().getValue();
+ final String entityId = trustChain.get(0).getEntityID().getValue();
assert entityId != null;
final Map<String, String> trustMarks = buildTrustMarks(entityId, trustChainContext.getVerifiedTrustMarks());
if (trustMarks != null && !trustMarks.isEmpty()) {
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 89551686..1f3e9f4f 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
@@ -45,12 +45,17 @@ public class OidFederationEventIds {
@Nonnull @NotEmpty public static final String INVALID_SUBJECT = "InvalidSubject";
/**
- * ID of event returned if the given trust anchor is invalid.
+ * ID of event returned if the given metadata is invalid.
+ */
+ @Nonnull @NotEmpty public static final String INVALID_METADATA = "InvalidMetadata";
+
+ /**
+ * ID of event returned if the given metadata policy is invalid.
*/
@Nonnull @NotEmpty public static final String INVALID_METADATA_POLICY = "InvalidMetadataPolicy";
/**
- * ID of event returned if the given trust anchor is invalid.
+ * ID of event returned if the given metadata is invalid against policy.
*/
@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/RelyingPartyTrustChainContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java
index 8a6ff150..c9acadcb 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java
@@ -25,7 +25,6 @@ import org.opensaml.messaging.context.BaseContext;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import net.shibboleth.shared.collection.Pair;
@@ -40,10 +39,10 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
@Nullable private List<List<EntityStatement>> resolvedTrustChains;
/** Policy-compliant trust chains for the relying party. */
- @Nullable private List<Pair<List<EntityStatement>,OIDCClientInformation>> policyCompliantTrustChains;
+ @Nullable private List<Pair<List<EntityStatement>,Map<String,Map<String,Object>>>> policyCompliantTrustChains;
/** Selected trust chain for the relying party. */
- @Nullable private Pair<List<EntityStatement>,OIDCClientInformation> selectedTrustChain;
+ @Nullable private Pair<List<EntityStatement>,Map<String,Map<String,Object>>> selectedTrustChain;
/** Expiration instant for the selected metadata. */
@Nullable private Instant selectedMetadataExpiration;
@@ -84,7 +83,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
*
* @return the trust chains
*/
- @Nullable public List<Pair<List<EntityStatement>,OIDCClientInformation>> getPolicyCompliantTrustChains() {
+ @Nullable public List<Pair<List<EntityStatement>,Map<String,Map<String,Object>>>> getPolicyCompliantTrustChains() {
return policyCompliantTrustChains;
}
@@ -96,7 +95,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
* @return this context
*/
@Nonnull public RelyingPartyTrustChainContext setPolicyCompliantTrustChains(
- @Nullable final List<Pair<List<EntityStatement>,OIDCClientInformation>> chains) {
+ @Nullable final List<Pair<List<EntityStatement>,Map<String,Map<String,Object>>>> chains) {
policyCompliantTrustChains = chains;
return this;
}
@@ -106,19 +105,19 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
*
* @return the trust chain
*/
- @Nullable public Pair<List<EntityStatement>,OIDCClientInformation> getSelectedTrustChain() {
+ @Nullable public Pair<List<EntityStatement>,Map<String,Map<String,Object>>> getSelectedTrustChain() {
return selectedTrustChain;
}
/**
* Set the selected trust chain for the relying party.
*
- * @param chais the selected trust chain
+ * @param chain the selected trust chain
*
* @return this context
*/
@Nonnull public RelyingPartyTrustChainContext setSelectedTrustChains(
- @Nullable final Pair<List<EntityStatement>,OIDCClientInformation> chain) {
+ @Nullable final Pair<List<EntityStatement>,Map<String,Map<String,Object>>> chain) {
selectedTrustChain = chain;
return this;
}
@@ -178,7 +177,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
/**
* Set the verified trust marks for the selected trust chain.
*
- * @param trustmarks verified trust marks
+ * @param trustMarks verified trust marks
*
* @return this context
*/
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 66fbb71e..2fad9aae 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
@@ -19,6 +19,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -31,7 +32,6 @@ import org.slf4j.Logger;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityIDCriterion;
@@ -83,7 +83,7 @@ public class ResolveTrustChains extends AbstractTrustChainResolutionAction {
/** Strategy used to fetch entity configuration delivered to the trust chain cache. */
@Nonnull private Function<ProfileRequestContext, EntityStatement> entityConfigurationLookupStrategy;
- /** Condition to require entity configuration via {@link this#entityConfigurationLookupStrategy}. */
+ /** Condition to require entity configuration via {@link #entityConfigurationLookupStrategy}. */
@Nonnull private Predicate<ProfileRequestContext> requireEntityConfigurationCondition;
/** OAuth2 client id. */
@@ -156,7 +156,7 @@ public class ResolveTrustChains extends AbstractTrustChainResolutionAction {
}
/**
- * Set the condition to require entity configuration via {@link this#entityConfigurationLookupStrategy}.
+ * Set the condition to require entity configuration via {@link #entityConfigurationLookupStrategy}.
* @param predicate condition
*/
public void setRequireEntityConfigurationCondition(@Nonnull final Predicate<ProfileRequestContext> predicate) {
@@ -234,7 +234,8 @@ public class ResolveTrustChains extends AbstractTrustChainResolutionAction {
final RelyingPartyTrustChainContext trustChainContext =
getTrustChainContextCreationStrategy().apply(profileRequestContext);
trustChainContext.setResolvedTrustChains(cacheResult.get(0));
- final List<Pair<List<EntityStatement>, OIDCClientInformation>> policyCompliantChains = new ArrayList<>();
+ final List<Pair<List<EntityStatement>, Map<String,Map<String,Object>>>> policyCompliantChains =
+ new ArrayList<>();
String errorEventId = null;
for (final List<EntityStatement> chain : cacheResult.get(0)) {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
index 0b194d61..9757b225 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
@@ -39,7 +39,6 @@ import org.slf4j.Logger;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustMarksParsingStrategy;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityIDCriterion;
@@ -277,7 +276,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
return false;
}
- final Pair<List<EntityStatement>, OIDCClientInformation> selectedChain =
+ final Pair<List<EntityStatement>, Map<String,Map<String,Object>>> selectedChain =
trustChainContext.getSelectedTrustChain();
if (selectedChain == null || selectedChain.getFirst() == null) {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
index 5cba50d4..085ffdbc 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
@@ -15,6 +15,7 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
import java.util.List;
+import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -27,7 +28,6 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.slf4j.Logger;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultTrustChainSelectionStrategy;
import net.shibboleth.idp.profile.AbstractProfileAction;
@@ -60,7 +60,8 @@ public class SelectTrustChain extends AbstractProfileAction {
@Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextCreationStrategy;
/** Strategy used to fetch the selected trust chain and metadata. */
- @NonnullAfterInit private Function<ProfileRequestContext,Pair<List<EntityStatement>, OIDCClientInformation>>
+ @NonnullAfterInit
+ private Function<ProfileRequestContext,Pair<List<EntityStatement>, Map<String,Map<String,Object>>>>
selectedTrustChainLookupStrategy;
/** Trust chain context to operate on. */
@@ -113,7 +114,7 @@ public class SelectTrustChain extends AbstractProfileAction {
* @param strategy lookup strategy
*/
public void setSelectedTrustChainLookupStrategy(@Nonnull final
- Function<ProfileRequestContext,Pair<List<EntityStatement>, OIDCClientInformation>> strategy) {
+ Function<ProfileRequestContext,Pair<List<EntityStatement>, Map<String,Map<String,Object>>>> strategy) {
checkSetterPreconditions();
selectedTrustChainLookupStrategy =
Constraint.isNotNull(strategy, "SelectedTrustChainLookupStrategy cannot be null");
@@ -146,7 +147,7 @@ public class SelectTrustChain extends AbstractProfileAction {
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- final Pair<List<EntityStatement>, OIDCClientInformation> selectedChain =
+ final Pair<List<EntityStatement>, Map<String,Map<String,Object>>> selectedChain =
selectedTrustChainLookupStrategy.apply(profileRequestContext);
if (selectedChain == null || selectedChain.getFirst() == null || selectedChain.getSecond() == null) {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java
index 5074365e..6be3b655 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java
@@ -16,6 +16,7 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
import java.time.Instant;
import java.util.List;
+import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -27,9 +28,12 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.slf4j.Logger;
+import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainMetadataLookupStrategy;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.oidc.metadata.ClientInformationManager;
import net.shibboleth.oidc.metadata.ClientInformationManagerException;
@@ -55,6 +59,9 @@ public class StoreAutomaticRegistration extends AbstractProfileAction {
/** Strategy used to lookup the trust chain context. */
@Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+ /** Strategy used to lookup the selected metadata. */
+ @Nonnull private Function<ProfileRequestContext, OIDCClientMetadata> selectedMetadataLookupStrategy;
+
/** The client information to be stored. */
@NonnullBeforeExec private OIDCClientInformation clientInformation;
@@ -70,6 +77,7 @@ public class StoreAutomaticRegistration extends AbstractProfileAction {
new InboundMessageContextLookup());
assert tcls != null;
trustChainContextLookupStrategy = tcls;
+ selectedMetadataLookupStrategy = new DefaultSelectedTrustChainMetadataLookupStrategy();
}
/**
@@ -90,6 +98,30 @@ public class StoreAutomaticRegistration extends AbstractProfileAction {
clientInformationManager = Constraint.isNotNull(manager, "The client information manager cannot be null!");
}
+ /**
+ * Set the strategy used to lookup the 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");
+ }
+
+ /**
+ * Set the strategy used to lookup the selected metadata.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSelectedMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCClientMetadata> strategy) {
+ checkSetterPreconditions();
+ selectedMetadataLookupStrategy =
+ Constraint.isNotNull(strategy, "SelectedMetadataLookupStrategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
@@ -115,16 +147,20 @@ public class StoreAutomaticRegistration extends AbstractProfileAction {
return false;
}
- final Pair<List<EntityStatement>,OIDCClientInformation> selectedTrustChain =
- trustChainContext.getSelectedTrustChain();
- assert selectedTrustChain != null;
- clientInformation = selectedTrustChain.getSecond();
- if (clientInformation == null) {
- log.error("{} Unable to locate selected metadata", getLogPrefix());
+ final OIDCClientMetadata metadata = selectedMetadataLookupStrategy.apply(profileRequestContext);
+ if (metadata == null) {
+ log.error("{} Unable to parse the RP metadata for storing", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
}
+ final Pair<List<EntityStatement>,Map<String,Map<String,Object>>> selectedTrustChain =
+ trustChainContext.getSelectedTrustChain();
+ assert selectedTrustChain != null;
+
+ clientInformation = new OIDCClientInformation(
+ new ClientID(selectedTrustChain.getFirst().get(0).getEntityID().getValue()), metadata);
+
expiration = trustChainContext.getSelectedMetadataExpiration();
if (expiration == null) {
log.error("{} Unable to resolve expiration time for the selected metadata", getLogPrefix());
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
index 0e4e4ad4..530c3ca3 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
@@ -29,10 +29,13 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.slf4j.Logger;
+import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.MandatoryTrustMarksLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainMetadataLookupStrategy;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.LocalMetadataPolicyLookupFunction;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
@@ -69,6 +72,9 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
/** Strategy used to lookup the trust chain context. */
@Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+ /** Strategy used to lookup the selected metadata. */
+ @Nonnull private Function<ProfileRequestContext, OIDCClientMetadata> selectedMetadataLookupStrategy;
+
/** Strategy used to lookup mandatory trust marks. */
@Nonnull private Function<ProfileRequestContext, List<String>> mandatoryTrustMarksLookupStrategy;
@@ -83,10 +89,7 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
@NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
/** Selected trust chain to operate on. */
- @NonnullBeforeExec private Pair<List<EntityStatement>, OIDCClientInformation> selectedTrustChain;
-
- /** Client ID of the client. */
- @NonnullBeforeExec private String clientId;
+ @NonnullBeforeExec private Pair<List<EntityStatement>, Map<String,Map<String,Object>>> selectedTrustChain;
/**
* Constructor.
@@ -97,6 +100,7 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
new InboundMessageContextLookup());
assert tcls != null;
trustChainContextLookupStrategy = tcls;
+ selectedMetadataLookupStrategy = new DefaultSelectedTrustChainMetadataLookupStrategy();
mandatoryTrustMarksLookupStrategy = new MandatoryTrustMarksLookupFunction();
localMetadataPolicyLookupStrategy = new LocalMetadataPolicyLookupFunction();
}
@@ -113,6 +117,18 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
}
+ /**
+ * Set the strategy used to lookup the selected metadata.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSelectedMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCClientMetadata> strategy) {
+ checkSetterPreconditions();
+ selectedMetadataLookupStrategy =
+ Constraint.isNotNull(strategy, "SelectedMetadataLookupStrategy cannot be null");
+ }
+
/**
* Set the strategy used to lookup mandatory trust marks.
*
@@ -171,30 +187,35 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
return false;
}
- final OIDCClientInformation clientInformation = selectedTrustChain.getSecond();
- assert clientInformation != null;
- clientId = clientInformation.getID().getValue();
-
return true;
}
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final OIDCClientMetadata metadata = selectedMetadataLookupStrategy.apply(profileRequestContext);
+ if (metadata == null) {
+ log.error("{} Unable to parse the RP metadata for storing", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return;
+ }
+ final OIDCClientInformation clientInformation = new OIDCClientInformation(
+ new ClientID(selectedTrustChain.getFirst().get(0).getEntityID().getValue()), metadata);
+
final Map<String, MetadataPolicy> localMetadataPolicy =
localMetadataPolicyLookupStrategy.apply(profileRequestContext);
if (localMetadataPolicy != null && !localMetadataPolicy.isEmpty()) {
log.debug("{} Applying local metadata policy into the client metadata", getLogPrefix());
final OIDCClientInformation enforcedMetadata =
- localMetadataPolicyMergingStrategy.apply(selectedTrustChain.getSecond(), localMetadataPolicy);
+ localMetadataPolicyMergingStrategy.apply(clientInformation, localMetadataPolicy);
if (enforcedMetadata == null) {
log.error("{} Could not apply the local metadata policy into the client metadata", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return;
}
- selectedTrustChain.setSecond(enforcedMetadata);
+ selectedTrustChain.setSecond(Map.of("openid_relying_party", clientInformation.toJSONObject()));
}
-
+ final String clientId = clientInformation.getID().getValue();
final List<String> mandatoryTrustMarks = mandatoryTrustMarksLookupStrategy.apply(profileRequestContext);
if (mandatoryTrustMarks != null && !mandatoryTrustMarks.isEmpty()) {
log.debug("{} Verifying the mandatory trust marks {}", getLogPrefix(), mandatoryTrustMarks);
@@ -221,7 +242,7 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
trustChainContext.setSelectedMetadataExpiration(resolveTrustChainExpiration(trustChain));
final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
- oidcCtx.setClientInformation(selectedTrustChain.getSecond());
+ oidcCtx.setClientInformation(clientInformation);
profileRequestContext.ensureInboundMessageContext().addSubcontext(oidcCtx);
log.debug("{} Client information attached to the OIDCMetadataContext", getLogPrefix());
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
index 9cfd6f4b..36384eca 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
@@ -16,6 +16,7 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.function.BiPredicate;
import java.util.function.Function;
@@ -26,7 +27,6 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
@@ -126,7 +126,8 @@ public class ValidateProvidedTrustChain extends AbstractTrustChainResolutionActi
getTrustChainContextCreationStrategy().apply(profileRequestContext);
assert trustChain != null;
trustChainContext.setResolvedTrustChains(CollectionSupport.listOf(trustChain));
- final List<Pair<List<EntityStatement>, OIDCClientInformation>> policyCompliantChains = new ArrayList<>();
+ final List<Pair<List<EntityStatement>, Map<String,Map<String,Object>>>> policyCompliantChains =
+ new ArrayList<>();
final String errorEventId = populatePolicyComplaintChains(trustChain, policyCompliantChains);
if (errorEventId != null) {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateResolveEntityRequest.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateResolveEntityRequest.java
index 02112173..846aac2a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateResolveEntityRequest.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateResolveEntityRequest.java
@@ -96,7 +96,7 @@ public class ValidateResolveEntityRequest extends AbstractProfileAction {
/**
* Set the strategy used to lookup the trust chain context.
*
- * @param strategy lookup strategy
+ * @param cache lookup strategy
*/
public void setLocalTrustAnchorsCache(
@Nonnull final MetadataCache<Map<String, LocalKeyContainer>> cache) {
@@ -155,7 +155,7 @@ public class ValidateResolveEntityRequest extends AbstractProfileAction {
log.debug("{} The following trust anchors were validated: {}", getLogPrefix(), validatedAnchors);
resolveEntityContext.setValidatedRequest(
new ResolveEntityRequest(requestMessage.getEndpointURI(), requestMessage.getSubject(),
- validatedAnchors, requestMessage.getEntityType()));
+ validatedAnchors, requestMessage.getEntityTypes()));
}
/**
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java
index 88e63c4a..082cf41c 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java
@@ -16,6 +16,7 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -26,7 +27,6 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.slf4j.Logger;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityIDCriterion;
@@ -160,10 +160,10 @@ public class ValidateSelectedTrustChain extends AbstractProfileAction {
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
final RelyingPartyTrustChainContext trustChainContext =
trustChainContextLookupStrategy.apply(profileRequestContext);
- final Pair<List<EntityStatement>, OIDCClientInformation> selectedTrustChain =
+ final Pair<List<EntityStatement>, Map<String,Map<String,Object>>> selectedTrustChain =
trustChainContext != null ? trustChainContext.getSelectedTrustChain() : null;
if (selectedTrustChain == null || selectedTrustChain.getFirst() == null) {
- final List<Pair<List<EntityStatement>,OIDCClientInformation>> allChains =
+ final List<Pair<List<EntityStatement>,Map<String,Map<String,Object>>>> allChains =
trustChainContext != null ? trustChainContext.getPolicyCompliantTrustChains() : null;
if (allChains == null || allChains.isEmpty()) {
if (isSubjectValid(validatedRequest.getSubject())) {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultEntityTypesLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultEntityTypesLookupFunction.java
new file mode 100644
index 00000000..7ba6c819
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultEntityTypesLookupFunction.java
@@ -0,0 +1,46 @@
+/*
+ * 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.profile.navigate;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.NonnullSupplier;
+
+/**
+ * Default function to lookup entity types to be included to the response metadata.
+ */
+public class DefaultEntityTypesLookupFunction implements Function<ProfileRequestContext, List<String>> {
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public List<String> apply(@Nullable final ProfileRequestContext profileRequestContext) {
+ return Optional.ofNullable(profileRequestContext)
+ .map(prc -> prc.getInboundMessageContext())
+ .map(msgCtx -> msgCtx.getMessage())
+ .filter(ResolveEntityRequest.class::isInstance)
+ .map(ResolveEntityRequest.class::cast)
+ .map(req -> req.getEntityTypes())
+ .orElseGet(NonnullSupplier.of(CollectionSupport.emptyList()));
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultMetadataValidationCondition.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultMetadataValidationCondition.java
new file mode 100644
index 00000000..37330379
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultMetadataValidationCondition.java
@@ -0,0 +1,81 @@
+/*
+ * 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.profile.navigate;
+
+import java.util.Map;
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.as.AuthorizationServerMetadata;
+import com.nimbusds.oauth2.sdk.client.ClientMetadata;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Default validation condition for metadata entries by entity type. Validation is done by parsing the entity type
+ * into a corresponding Nimbus object.
+ */
+public class DefaultMetadataValidationCondition implements BiPredicate<String, Map<String,Object>> {
+
+ /** Class logger. */
+ @Nonnull private final static Logger log = LoggerFactory.getLogger(DefaultMetadataValidationCondition.class);
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final String key, @Nullable final Map<String, Object> metadata) {
+ if (StringSupport.trimOrNull(key) == null || metadata == null) {
+ log.error("Invalid input to the validation condition key={}, metadata={}", key, metadata);
+ return false;
+ }
+ try {
+ switch (key) {
+ case "federation_entity":
+ log.debug("Ignoring validation of {}", key);
+ return true;
+ case "openid_provider":
+ OIDCProviderMetadata.parse(new JSONObject(metadata));
+ return true;
+ case "openid_relying_party":
+ OIDCClientMetadata.parse(new JSONObject(metadata));
+ return true;
+ case "oauth_authorization_server":
+ AuthorizationServerMetadata.parse(new JSONObject(metadata));
+ return true;
+ case "oauth_client":
+ ClientMetadata.parse(new JSONObject(metadata));
+ return true;
+ case "oauth_resource":
+ log.debug("Ignoring validation of {}", key);
+ return true;
+ default:
+ log.debug("Ignoring validation of {}", key);
+ return true;
+ }
+ } catch (final ParseException e) {
+ log.warn("Could not parse entity_type {}", key, e);
+ }
+ return false;
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultSelectedTrustChainMetadataLookupStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultSelectedTrustChainMetadataLookupStrategy.java
index 205c2c6c..bd42f6ee 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultSelectedTrustChainMetadataLookupStrategy.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultSelectedTrustChainMetadataLookupStrategy.java
@@ -14,13 +14,16 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate;
+import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+import net.minidev.json.JSONObject;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext;
/**
@@ -35,7 +38,17 @@ public class DefaultSelectedTrustChainMetadataLookupStrategy
public OIDCClientMetadata doApply(@Nonnull final RelyingPartyTrustChainContext trustChainContext) {
return Optional.ofNullable(trustChainContext.getSelectedTrustChain())
.map(pair -> pair.getSecond())
- .map(clientInfo -> clientInfo.getOIDCMetadata())
+ .map(map -> map.get("openid_relying_party"))
+ .filter(Map.class::isInstance)
+ .map(Map.class::cast)
+ .map(JSONObject::new)
+ .map(json-> {
+ try {
+ return OIDCClientMetadata.parse(json);
+ } catch (ParseException e) {
+ return null;
+ }
+ })
.orElse(null);
}
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
index 3bbe1cd5..a3a82549 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
@@ -15,6 +15,7 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate;
import java.util.List;
+import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -22,7 +23,6 @@ import javax.annotation.Nullable;
import org.slf4j.Logger;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext;
import net.shibboleth.shared.collection.Pair;
@@ -34,16 +34,16 @@ import net.shibboleth.shared.primitive.LoggerFactory;
* {@link RelyingPartyTrustChainContext#getRejectedTrustChains()}.
*/
public class DefaultTrustChainSelectionStrategy
- extends AbstractTrustChainContextLookupFunction<Pair<List<EntityStatement>, OIDCClientInformation>> {
+ extends AbstractTrustChainContextLookupFunction<Pair<List<EntityStatement>, Map<String,Map<String,Object>>>> {
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainSelectionStrategy.class);
/** {@inheritDoc} */
@Override @Nullable
- public Pair<List<EntityStatement>, OIDCClientInformation> doApply(
+ public Pair<List<EntityStatement>, Map<String,Map<String,Object>>> doApply(
@Nonnull final RelyingPartyTrustChainContext trustChainContext) {
- final List<Pair<List<EntityStatement>, OIDCClientInformation>> policyCompliantChains =
+ final List<Pair<List<EntityStatement>, Map<String,Map<String,Object>>>> policyCompliantChains =
trustChainContext.getPolicyCompliantTrustChains();
if (policyCompliantChains == null || policyCompliantChains.isEmpty()) {
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 6ef357d6..dd0c2d13 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
@@ -781,6 +781,12 @@
class="org.springframework.beans.factory.config.MapFactoryBean">
<property name="sourceMap">
<map merge="true" value-type="com.nimbusds.oauth2.sdk.ErrorObject">
+ <entry>
+ <key>
+ <util:constant static-field="org.opensaml.profile.action.EventIds.INVALID_MSG_CTX"/>
+ </key>
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="server_error" c:_1="Internal server error" c:_2="500" />
+ </entry>
<entry>
<key>
<util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.INVALID_TRUST_ANCHOR"/>
@@ -793,6 +799,12 @@
</key>
<bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_subject" c:_1="Subject in the request is invalid" c:_2="404" />
</entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.OidFederationEventIds.INVALID_METADATA"/>
+ </key>
+ <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="invalid_metadata" c:_1="Metadata is invalid or not found for the requested entity types" 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 526384c2..c30d807f 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
@@ -89,7 +89,16 @@
p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"
p:preSelectedTrustChainIdsLookupStrategy="#{getObject('shibboleth.oidfed.PreSelectedTrustChainIDsLookupStrategy') ?: getObject('shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy')}"
- p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}"/>
+ p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}"
+ p:metadataValidationCondition-ref="#{'%{idp.oidfed.MetadataValidationCondition:DefaultMetadataValidationCondition}'.trim()}">
+ <property name="metadataLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultCombinedMetadataFromTrustChainLookupStrategy"
+ p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
+ </property>
+ </bean>
+
+ <bean id="DefaultMetadataValidationCondition"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultMetadataValidationCondition" />
<bean id="DefaultMetadataPolicyEnforcer"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyEnforcer"
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 c5424448..082d9956 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
@@ -41,7 +41,9 @@
scope="prototype"
p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.register.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.register.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"
- p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}">
+ p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}"
+ p:metadataLookupStrategy-ref="DefaultCombinedMetadataFromTrustChainLookupStrategy"
+ p:metadataValidationCondition-ref="#{'%{idp.oidfed.MetadataValidationCondition:DefaultMetadataValidationCondition}'.trim()}">
<property name="providedTrustChainValidationStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultProvidedTrustChainValidationStrategy"
p:federationPolicyConstraints-ref="%{idp.oidfed.FederationPolicyConstraints:shibboleth.oidfed.DefaultFederationPolicyConstraints}"
@@ -74,6 +76,13 @@
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.ExplicitClientRegistrationRequestTrustChainLookupFunction" />
</property>
</bean>
+
+ <bean id="DefaultMetadataValidationCondition"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultMetadataValidationCondition" />
+
+ <bean id="DefaultCombinedMetadataFromTrustChainLookupStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultCombinedMetadataFromTrustChainLookupStrategy"
+ p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
<bean id="ResolveTrustChains" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustChains"
scope="prototype"
@@ -83,7 +92,9 @@
p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.register.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"
p:preSelectedTrustChainIdsLookupStrategy="#{getObject('shibboleth.oidfed.PreSelectedTrustChainIDsLookupStrategy') ?: getObject('shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy')}"
p:requireEntityConfigurationCondition-ref="shibboleth.Conditions.TRUE"
- p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}">
+ p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}"
+ p:metadataLookupStrategy-ref="DefaultCombinedMetadataFromTrustChainLookupStrategy"
+ p:metadataValidationCondition-ref="#{'%{idp.oidfed.MetadataValidationCondition:DefaultMetadataValidationCondition}'.trim()}">
<property name="entityConfigurationLookupStrategy">
<bean parent="shibboleth.Functions.Expression"
c:expression="#custom.apply(#input.ensureInboundMessageContext().getMessage().getEntityConfiguration(), null)">
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 dc13f569..f9606281 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
@@ -80,7 +80,16 @@
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')}"
- p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}"/>
+ p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}"
+ p:metadataValidationCondition-ref="#{'%{idp.oidfed.MetadataValidationCondition:DefaultMetadataValidationCondition}'.trim()}">
+ <property name="metadataLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultCombinedMetadataFromTrustChainLookupStrategy"
+ p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
+ </property>
+ </bean>
+
+ <bean id="DefaultMetadataValidationCondition"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultMetadataValidationCondition" />
<bean id="DefaultMetadataPolicyEnforcer"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyEnforcer"
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 e15f8217..204714e0 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
@@ -64,7 +64,10 @@ import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.openid.connect.sdk.SubjectType;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
import net.minidev.json.JSONObject;
@@ -223,6 +226,41 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
return rpConfiguration.getSignedStatement().serialize();
}
+ protected String opEntityConfiguration(final String issuer, final String... authorityHints)
+ throws URISyntaxException {
+ return opEntityConfiguration(issuer, emptyOpMetadata(issuer), authorityHints);
+ }
+
+ protected String opEntityConfiguration(final String issuer, final OIDCProviderMetadata metadata,
+ final String... authorityHints) throws URISyntaxException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(issuer).subject(issuer)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("openid_provider", metadata.toJSONObject()))
+ .claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
+ new String[] { anchorId } : authorityHints)
+ .build();
+ final EntityStatement rpConfiguration =
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, claimsSet);
+ return rpConfiguration.getSignedStatement().serialize();
+ }
+
+ protected String entityConfiguration(final String entityId, final Map<String, Object> metadata,
+ 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(leafKey).toJSONObject(true))
+ .claim("metadata", metadata)
+ .claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
+ new String[] { anchorId } : authorityHints)
+ .build();
+ final EntityStatement configuration =
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, claimsSet);
+ return configuration.getSignedStatement().serialize();
+ }
+
protected String trustedAnchorConfiguration() {
final String anchorId = "https://trust-anchor.federation.local";
final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(anchorId).subject(anchorId)
@@ -266,12 +304,12 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
return anchorConfiguration.getSignedStatement().serialize();
}
- protected String subordinateStatement(final String clientId) {
- final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(anchorId).subject(clientId)
+ protected String subordinateStatement(final String issuer, final Map<String, Object> metadata) {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(anchorId).subject(issuer)
.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", new OIDCClientMetadata().toJSONObject()))
+ .claim("metadata", metadata)
.claim("authority_hints", new String[] { anchorId })
.build();
final EntityStatement rpConfiguration =
@@ -279,14 +317,14 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
return rpConfiguration.getSignedStatement().serialize();
}
- protected String subordinateStatement(final String issuer, final JWK issuerKey, final JWK subjetKey,
+ protected String rpSubordinateStatement(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("metadata_policy", rpPolicy)
.claim("authority_hints", authorityHints)
.build();
final EntityStatement rpConfiguration =
@@ -302,7 +340,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
.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("metadata_policy", rpPolicy)
.claim("authority_hints", authorityHints)
.claim("constraints", constraints)
.build();
@@ -319,54 +357,68 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
return String.format(intermediateIdPattern, intermediateIndex.getAndIncrement());
}
- protected void configureMockHttpClient(final String clientId) {
+ protected void rpConfigureMockHttpClient(final String clientId) {
try {
mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration(clientId)));
mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
mapResponse(subordinateStatementUrl(anchorFetchEndpoint, clientId),
- mockResponse(subordinateStatement(clientId)));
+ mockResponse(subordinateStatement(clientId, Map.of("openid_relying_party",
+ new OIDCClientMetadata().toJSONObject()))));
} catch (UnsupportedOperationException | IOException | URISyntaxException e) {
Assert.fail("Could not initialize mock HTTP client", e);
}
}
- protected void configureMockHttpClient(final String clientId, final OIDCClientMetadata metadata) {
+ protected void rpConfigureMockHttpClient(final String clientId, final OIDCClientMetadata metadata) {
try {
mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration(clientId, metadata)));
mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
mapResponse(subordinateStatementUrl(anchorFetchEndpoint, clientId),
- mockResponse(subordinateStatement(clientId)));
+ mockResponse(subordinateStatement(clientId, Map.of("openid_relying_party",
+ new OIDCClientMetadata().toJSONObject()))));
+ } catch (UnsupportedOperationException | IOException | URISyntaxException e) {
+ Assert.fail("Could not initialize mock HTTP client", e);
+ }
+ }
+
+ protected void rpConfigureMockHttpClient(final String clientId, final JSONObject metadata) {
+ try {
+ mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration(clientId)));
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, clientId),
+ mockResponse(subordinateStatement(clientId, Map.of("openid_relying_party", metadata))));
} catch (UnsupportedOperationException | IOException | URISyntaxException e) {
Assert.fail("Could not initialize mock HTTP client", e);
}
}
- protected void configureMockHttpClient(final String clientId, final String rpEntityConfiguration) {
+ protected void rpConfigureMockHttpClient(final String clientId, final String rpEntityConfiguration) {
try {
mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration));
mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
mapResponse(subordinateStatementUrl(anchorFetchEndpoint, clientId),
- mockResponse(subordinateStatement(clientId)));
+ mockResponse(subordinateStatement(clientId, Map.of("openid_relying_party",
+ new OIDCClientMetadata().toJSONObject()))));
} catch (UnsupportedOperationException | IOException e) {
Assert.fail("Could not initialize mock HTTP client", e);
}
}
- protected void configureMockHttpClientWithAnchorConstraints(final String clientId,
+ protected void rpConfigureMockHttpClientWithAnchorConstraints(final String clientId,
final Map<String,Object> constraints) {
try {
mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration(clientId)));
mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
mapResponse(subordinateStatementUrl(anchorFetchEndpoint, clientId),
mockResponse(subordinateStatement(anchorId, trustedAnchorKey, leafKey, clientId,
- Collections.emptyMap(), constraints)));
+ Map.of("openid_relying_party", Collections.emptyMap()), constraints)));
} catch (UnsupportedOperationException | IOException | URISyntaxException e) {
Assert.fail("Could not initialize mock HTTP client", e);
}
}
@SuppressWarnings("unchecked")
- protected void configureMockHttpClient(final String clientId, final Map<String, Object> testVector) {
+ protected void rpConfigureMockHttpClient(final String clientId, final Map<String, Object> testVector) {
final String intermediateId = uniqueIntermediateId();
try {
final Map<String, Object> vectorMetadata = (Map<String, Object>) testVector.get("metadata");
@@ -384,16 +436,82 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
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)));
+ mockResponse(rpSubordinateStatement(anchorId, trustedAnchorKey, intermediateKey, intermediateId,
+ Map.of("openid_relying_party", (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)));
+ mockResponse(rpSubordinateStatement(intermediateId, intermediateKey, rpKey, clientId,
+ Map.of("openid_relying_party", (Map<String, Object>) testVector.get("INT")),
+ intermediateId)));
} catch (UnsupportedOperationException | IOException | URISyntaxException | ParseException e) {
Assert.fail("Could not initialize mock HTTP client", e);
}
}
+ protected OIDCProviderMetadata emptyOpMetadata(final String issuer) {
+ return new OIDCProviderMetadata(new Issuer(issuer), List.of(SubjectType.PUBLIC),
+ URI.create("https://mock.example.org/jwks"));
+ }
+
+ protected void opConfigureMockHttpClient(final String issuer) {
+ try {
+ mapResponse(entityConfigurationUrl(issuer), mockResponse(opEntityConfiguration(issuer)));
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, issuer),
+ mockResponse(subordinateStatement(issuer, Map.of("openid_provider",
+ emptyOpMetadata(issuer).toJSONObject()))));
+ } catch (UnsupportedOperationException | IOException | URISyntaxException e) {
+ Assert.fail("Could not initialize mock HTTP client", e);
+ }
+ }
+
+ protected void opConfigureMockHttpClient(final String issuer, final OIDCProviderMetadata metadata) {
+ try {
+ mapResponse(entityConfigurationUrl(issuer), mockResponse(opEntityConfiguration(issuer, metadata)));
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, issuer),
+ mockResponse(subordinateStatement(issuer, Map.of("openid_provider",
+ metadata.toJSONObject()))));
+ } catch (UnsupportedOperationException | IOException | URISyntaxException e) {
+ Assert.fail("Could not initialize mock HTTP client", e);
+ }
+ }
+
+ protected void opConfigureMockHttpClient(final String issuer, final JSONObject metadata) {
+ try {
+ mapResponse(entityConfigurationUrl(issuer), mockResponse(opEntityConfiguration(issuer)));
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, issuer),
+ mockResponse(subordinateStatement(issuer, Map.of("openid_provider", metadata))));
+ } catch (UnsupportedOperationException | IOException | URISyntaxException e) {
+ Assert.fail("Could not initialize mock HTTP client", e);
+ }
+ }
+
+ protected void opConfigureMockHttpClient(final String issuer, final String opEntityConfiguration) {
+ try {
+ mapResponse(entityConfigurationUrl(issuer), mockResponse(opEntityConfiguration));
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, issuer),
+ mockResponse(subordinateStatement(issuer, Map.of("openid_provider",
+ emptyOpMetadata(issuer).toJSONObject()))));
+ } catch (UnsupportedOperationException | IOException e) {
+ Assert.fail("Could not initialize mock HTTP client", e);
+ }
+ }
+
+ protected void opConfigureMockHttpClientWithAnchorConstraints(final String issuer,
+ final Map<String,Object> constraints) {
+ try {
+ mapResponse(entityConfigurationUrl(issuer), mockResponse(opEntityConfiguration(issuer)));
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, issuer),
+ mockResponse(subordinateStatement(anchorId, trustedAnchorKey, leafKey, issuer,
+ Map.of("openid_provider", Collections.emptyMap()), constraints)));
+ } catch (UnsupportedOperationException | IOException | URISyntaxException 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(
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
index dfba9965..1d627309 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
@@ -58,7 +58,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_noRequestObject()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result = launchAuthenticationRequest(clientId, "openid profile");
Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
}
@@ -67,7 +67,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_plainRequestObject()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", plainRequestObject(Map.of(
"iss", clientId,
@@ -84,7 +84,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_signedRequestObject()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -111,7 +111,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
final OIDCClientMetadata metadata = new OIDCClientMetadata();
metadata.setRedirectionURI(new URI(redirectUri));
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
- configureMockHttpClient(clientId, rpEntityConfigurationUnmatchingKey(clientId, metadata));
+ rpConfigureMockHttpClient(clientId, rpEntityConfigurationUnmatchingKey(clientId, metadata));
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -129,7 +129,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_signedRequestObject_nonMatchingClientId()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId + "2", "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -147,7 +147,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_signedRequestObject_missingJti()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -164,7 +164,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_signedRequestObject_missingExp()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -181,7 +181,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_signedRequestObject_invalidIss()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId + "2",
@@ -199,7 +199,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_signedRequestObject_missingIss()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"client_id", clientId,
@@ -216,7 +216,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_signedRequestObject_invalidClientId()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -234,7 +234,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_signedRequestObject_missingClientId()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -251,7 +251,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_signedRequestObject_containsForbiddenSub()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -270,7 +270,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_signedRequestObject_containsAdditionalAudience()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -288,7 +288,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_leafKeySignedRequestObject()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -303,7 +303,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithPar_unresolvableTrustChain()
throws Exception {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final URI uri = createParGeneratedRequestUri(Map.of(
"client_id", clientId,
"response_type", "code",
@@ -317,7 +317,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithInvalidTrustChain_entityTypeConstraint_signedRequestObject()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClientWithAnchorConstraints(clientId, Map.of("allowed_entity_types", Collections.emptyList()));
+ rpConfigureMockHttpClientWithAnchorConstraints(clientId, Map.of("allowed_entity_types", Collections.emptyList()));
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
@@ -335,7 +335,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithInvalidTrustChain_namingConstraint_signedRequestObject()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClientWithAnchorConstraints(clientId, Map.of("naming_constraints",
+ rpConfigureMockHttpClientWithAnchorConstraints(clientId, Map.of("naming_constraints",
Map.of("permitted", List.of(".wrongfederation.local"))));
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
@@ -354,7 +354,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithValidTrustChain_withConstraints_signedRequestObject()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClientWithAnchorConstraints(clientId,
+ rpConfigureMockHttpClientWithAnchorConstraints(clientId,
Map.of("naming_constraints", Map.of("permitted", List.of(".federation.local")),
"allowed_entity_types", List.of("openid_relying_party")));
final FlowExecutionResult result =
@@ -380,7 +380,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
public void testWithPar_matchingAutoRegisteredTrustChain()
throws IOException, UnsupportedOperationException, URISyntaxException {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final FlowExecutionResult result =
launchAuthenticationRequest(clientId, "openid profile", createParGeneratedRequestUri(Map.of(
"client_id", clientId,
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 e5a15c96..68a00117 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
@@ -58,7 +58,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
@Test
public void testSuccess() throws Exception {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final SignedJWT jwt = createPrivateKeyJWT(validClaimsSet(clientId, issuer),
rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
@@ -75,7 +75,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
final OIDCClientMetadata metadata = new OIDCClientMetadata();
metadata.setRedirectionURI(new URI(redirectUri));
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
- configureMockHttpClient(clientId, rpEntityConfigurationUnmatchingKey(clientId, metadata));
+ rpConfigureMockHttpClient(clientId, rpEntityConfigurationUnmatchingKey(clientId, metadata));
final SignedJWT jwt = createPrivateKeyJWT(validClaimsSet(clientId, issuer),
rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
@@ -91,7 +91,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId));
final FlowExecutionResult result =
flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
@@ -106,7 +106,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
"client_id", clientId,
"iss", clientId,
@@ -132,7 +132,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
"client_id", clientId,
"iss", clientId,
@@ -154,7 +154,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
"client_id", clientId,
"aud", issuer,
@@ -176,7 +176,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
"client_id", clientId,
"iss", clientId + "2",
@@ -199,7 +199,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
"iss", clientId,
"aud", issuer,
@@ -221,7 +221,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
"client_id", clientId + "2",
"iss", clientId,
@@ -244,7 +244,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
"client_id", clientId,
"iss", clientId,
@@ -266,7 +266,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
"client_id", clientId,
"sub", "mockValue",
@@ -290,7 +290,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
"client_id", clientId,
"iss", clientId,
@@ -313,7 +313,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, metadata);
+ rpConfigureMockHttpClient(clientId, metadata);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", plainRequestObject(Map.of(
"client_id", clientId,
"iss", clientId,
@@ -335,7 +335,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
initializeMocks();
initializeThreadLocals();
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId, vector);
+ rpConfigureMockHttpClient(clientId, vector);
final SignedJWT jwt = createPrivateKeyJWT(validClaimsSet(clientId, issuer),
rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS256);
final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
index e0717249..0e5e013c 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
@@ -78,7 +78,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testInvalidEntityConfiguration_wrongSignerKey() throws Exception {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final OIDCClientMetadata metadata = new OIDCClientMetadata();
metadata.setRedirectionURI(new URI(redirectUri));
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
@@ -93,7 +93,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testValidEntityConfiguration_invalidType() throws Exception {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
setRequest("POST", rpEntityConfiguration(clientId), "application/json");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertErrorCode(result, "invalid_client_metadata");
@@ -102,7 +102,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testValidEntityConfiguration() throws Exception {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
verify(federationHttpClient, times(0)).executeOpen(any(),
@@ -113,7 +113,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testValidEntityConfiguration_customScope() throws Exception {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final OIDCClientMetadata metadata = new OIDCClientMetadata();
metadata.setRedirectionURI(new URI(redirectUri));
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
@@ -132,7 +132,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
public void testValidEntityConfiguration_repeat() throws Exception {
final String clientId = uniqueClientId();
for (int i = 0; i < 2; i++) {
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
verify(federationHttpClient, times(0)).executeOpen(any(),
@@ -145,7 +145,8 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
public void testValidTrustChain() throws Exception {
final String clientId = uniqueClientId();
final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
- subordinateStatement(clientId) + "\", \"" + trustedAnchorConfiguration() + "\"]";
+ subordinateStatement(clientId, Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ + "\", \"" + trustedAnchorConfiguration() + "\"]";
setRequest("POST", trustChain, "application/trust-chain+json");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
verify(federationHttpClient, times(0)).executeOpen(any(),
@@ -255,12 +256,13 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testInvalidTrustChain_wrongRpEntityConfigurationSignerKey() throws Exception {
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
final OIDCClientMetadata metadata = new OIDCClientMetadata();
metadata.setRedirectionURI(new URI(redirectUri));
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
final String trustChain = "[\"" + rpEntityConfigurationUnmatchingKey(clientId, metadata) + "\", \"" +
- subordinateStatement(clientId) + "\", \"" + trustedAnchorConfiguration() + "\"]";
+ subordinateStatement(clientId, Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ + "\", \"" + trustedAnchorConfiguration() + "\"]";
setRequest("POST", trustChain, "application/trust-chain+json");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
verify(federationHttpClient, times(0)).executeOpen(any(),
@@ -279,7 +281,8 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
final Scope scope = Scope.parse("openid profile email custom");
metadata.setScope(scope);
final String trustChain = "[\"" + rpEntityConfiguration(clientId, metadata) + "\", \"" +
- subordinateStatement(clientId) + "\", \"" + trustedAnchorConfiguration() + "\"]";
+ subordinateStatement(clientId, Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ + "\", \"" + trustedAnchorConfiguration() + "\"]";
setRequest("POST", trustChain, "application/trust-chain+json");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
final OIDCClientMetadata providedMetadata = assertResponseStatement(
@@ -295,7 +298,8 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
public void testValidTrustChain_repeat() throws Exception {
final String clientId = uniqueClientId();
final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
- subordinateStatement(clientId) + "\", \"" + trustedAnchorConfiguration() + "\"]";
+ subordinateStatement(clientId, Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ + "\", \"" + trustedAnchorConfiguration() + "\"]";
for (int i = 0; i < 2; i++) {
setRequest("POST", trustChain, "application/trust-chain+json");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/ResolveEntityFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/ResolveEntityFlowTest.java
index 2cb4669d..93f2cb0d 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/ResolveEntityFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/ResolveEntityFlowTest.java
@@ -14,6 +14,9 @@
package net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed;
+import java.util.List;
+import java.util.Map;
+
import org.springframework.webflow.executor.FlowExecutionResult;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -24,6 +27,8 @@ import com.nimbusds.oauth2.sdk.Response;
import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityResponse;
import net.shibboleth.oidc.profile.messaging.JSONErrorResponse;
+import net.minidev.json.JSONObject;
+
/**
* Flow tests for the OpenID federation resolve entity flow.
*/
@@ -54,23 +59,58 @@ public class ResolveEntityFlowTest extends AbstractFederationFlowTest {
public void testUntrustedAnchor() throws Exception {
request.setMethod("GET");
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
request.setQueryString("sub=" + clientId + "&trust_anchor=mockAnchors&entity_type=openid_relying_party");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertErrorCode(result, "invalid_trust_anchor");
}
@Test
- public void testWithTrustedTrustAnchor() throws Exception {
+ public void testRPWithTrustedTrustAnchor() throws Exception {
request.setMethod("GET");
final String clientId = uniqueClientId();
- configureMockHttpClient(clientId);
+ rpConfigureMockHttpClient(clientId);
request.setQueryString("sub=" + clientId + "&trust_anchor=" + anchorId + "&entity_type=openid_relying_party");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
final ResolveEntityResponse parsedResponse =
parseSuccessResponse(result, ResolveEntityResponse.class);
final SignedJWT response = parsedResponse.getJWT();
Assert.assertEquals(response.getJWTClaimsSet().getSubject(), clientId);
+ Assert.assertNotNull(response.getJWTClaimsSet().getClaim("metadata"));
+ }
+
+ @Test
+ public void testRPWithTrustedTrustAnchorInvalidMetadata() throws Exception {
+ request.setMethod("GET");
+ final String clientId = uniqueClientId();
+ rpConfigureMockHttpClient(clientId, new JSONObject(Map.of("response_types", "invalid")));
+ request.setQueryString("sub=" + clientId + "&trust_anchor=" + anchorId + "&entity_type=openid_relying_party");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, "invalid_metadata");
+ }
+
+ @Test
+ public void testOPWithTrustedTrustAnchor() throws Exception {
+ request.setMethod("GET");
+ final String entityId = uniqueClientId();
+ opConfigureMockHttpClient(entityId);
+ request.setQueryString("sub=" + entityId + "&trust_anchor=" + anchorId + "&entity_type=openid_provider");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final ResolveEntityResponse parsedResponse =
+ parseSuccessResponse(result, ResolveEntityResponse.class);
+ final SignedJWT response = parsedResponse.getJWT();
+ Assert.assertEquals(response.getJWTClaimsSet().getSubject(), entityId);
+ Assert.assertNotNull(response.getJWTClaimsSet().getClaim("metadata"));
+ }
+
+ @Test
+ public void testOPWithTrustedTrustAnchorInvalidMetadata() throws Exception {
+ request.setMethod("GET");
+ final String entityId = uniqueClientId();
+ opConfigureMockHttpClient(entityId, new JSONObject(Map.of("issuer", List.of("unexpected", "values"))));
+ request.setQueryString("sub=" + entityId + "&trust_anchor=" + anchorId + "&entity_type=openid_provider");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, "invalid_metadata");
}
protected JSONErrorResponse parseErrorResponse(final FlowExecutionResult result) {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list