[java-idp-oidc] branch dev/JOIDC-222 updated: JOIDC-222 - Support for OpenID Federation
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Feb 21 11:29:08 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=4b2f71a7aee947c5a5dabbc89e4e432a5705a03c
The following commit(s) were added to refs/heads/dev/JOIDC-222 by this push:
new 4b2f71a7 JOIDC-222 - Support for OpenID Federation
4b2f71a7 is described below
commit 4b2f71a7aee947c5a5dabbc89e4e432a5705a03c
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Feb 21 13:28:27 2025 +0200
JOIDC-222 - Support for OpenID Federation
https://shibboleth.atlassian.net/browse/JOIDC-222
Initial support for automatic registration via PAR endpoint
- PAR requires either client authentication or signed request object for automatic registration
- PAR endpoint stores the selected trust chain into the request_uri claims set
- Authorize-flow was refactored to deserialize PAR-produced request_uri before metadata resolution
- The previously selected trust chain can then be exploited during the metadata-lookup
- Deserialization is done by new DeserializePushedRequest -action
- Claims set is stored in OAuth2PushedAuthorizationRequestContext
- SetRequestObjectToResponseContext exploits the deserialized claims set
- Signed request object is always required with automatic registration
---
.../OAuth2PushedAuthorizationRequestContext.java | 60 ++++++++++
.../profile/impl/DeserializedPushedRequest.java | 128 +++++++++++++++++++++
...mOutbounPushedAuthorizationResponseMessage.java | 63 ++++++++--
.../impl/SetRequestObjectToResponseContext.java | 50 ++++----
...rizePreSelectedTrustChainIDsLookupStrategy.java | 52 +++++++++
.../pushed-authorization-beans.xml | 23 +++-
.../oidc/abstract-api/oidc-abstract-api-beans.xml | 3 +
.../idp/flows/oidc/authorize/authorize-beans.xml | 54 +++++++--
.../idp/flows/oidc/authorize/authorize-flow.xml | 1 +
.../oidc/metadata-lookup/metadata-lookup-beans.xml | 3 +-
10 files changed, 395 insertions(+), 42 deletions(-)
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2PushedAuthorizationRequestContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2PushedAuthorizationRequestContext.java
new file mode 100644
index 00000000..a4e7fc1a
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2PushedAuthorizationRequestContext.java
@@ -0,0 +1,60 @@
+/*
+ * 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.oauth2.messaging.context;
+
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.messaging.context.MessageContext;
+
+/**
+ * Subcontext carrying information for a pushed authorization request.
+ *
+ * <p>This context appears as a subcontext of an inbound {@link MessageContext}.</p>
+ *
+ * @since 4.3.0
+ */
+public class OAuth2PushedAuthorizationRequestContext extends BaseContext {
+
+ /** The claim set carried within pushed authorization request. */
+ @Nullable private Map<String,Object> claimsSet;
+
+ /**
+ * Get the claims set carried within pushed authorization request.
+ *
+ * @return claims set
+ */
+ @Nullable public Map<String,Object> getClaimsSet() {
+ return claimsSet;
+ }
+
+ /**
+ * Set the claims set carried within pushed authorization request.
+ *
+ * @param claims claims set
+ *
+ * @return this context
+ */
+ @Nonnull public OAuth2PushedAuthorizationRequestContext setClaimsSet(
+ @Nullable final Map<String,Object> claims) {
+ claimsSet = claims;
+
+ return this;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/DeserializedPushedRequest.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/DeserializedPushedRequest.java
new file mode 100644
index 00000000..3ea0aee8
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/DeserializedPushedRequest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.oauth2.profile.impl;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2PushedAuthorizationRequestContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Attempts to deserialize the possibly existing request_uri value from the authorization request via configurable
+ * list of pushed authorization deserializers. If any of the deserializer is successful, the extracted claims set is
+ * stored to {@link OAuth2PushedAuthorizationRequestContext}.
+ */
+public class DeserializedPushedRequest extends AbstractOAuthAuthorizationRequestAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DeserializedPushedRequest.class);
+
+ /** Lookup strategy for pushed authorization request context- */
+ @Nonnull private Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext>
+ pushedAuthorizationRequestContextLookupStrategy;
+
+ /** List of deserializers for OP-issued request_uri values. */
+ @Nonnull private List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>>
+ pushedAuthorizationRequestUriDeserializers;
+
+ /** Request URI to be deserialized. */
+ @NonnullBeforeExec private URI requestUri;
+
+ /**
+ * Constructor.
+ */
+ public DeserializedPushedRequest() {
+ final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> parcls =
+ new ChildContextLookup<>(OAuth2PushedAuthorizationRequestContext.class, true).compose(
+ new InboundMessageContextLookup());
+ assert parcls != null;
+ pushedAuthorizationRequestContextLookupStrategy = parcls;
+
+ pushedAuthorizationRequestUriDeserializers = CollectionSupport.emptyList();
+ }
+
+ /**
+ * Set the lookup strategy for pushed authorization request context.
+ *
+ * @param strategy the lookup strategy for pushed authorization request context
+ */
+ public void setPushedAuthorizationRequestContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> strategy) {
+ checkSetterPreconditions();
+ pushedAuthorizationRequestContextLookupStrategy = Constraint.isNotNull(strategy,
+ "Pushed authorization request context lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the list of deserializers for OP-issued request_uri values.
+ *
+ * @param deserializers What to set.
+ */
+ public void setPushedAuthorizationRequestUriDeserializers(
+ @Nonnull final List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>> deserializers) {
+ checkSetterPreconditions();
+ pushedAuthorizationRequestUriDeserializers = Constraint.isNotNull(deserializers,
+ "List of request_uri deserializers cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ requestUri = Optional.ofNullable(getAuthorizationRequest())
+ .map(request -> request.getRequestURI())
+ .orElse(null);
+
+ if (requestUri == null) {
+ log.debug("{} No request_uri value found, nothing to do", getLogPrefix());
+ return false;
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ for (final BiFunction<ProfileRequestContext,URI,Map<String,Object>> deserializer :
+ pushedAuthorizationRequestUriDeserializers) {
+ final Map<String,Object> claimsSet = deserializer.apply(profileRequestContext, requestUri);
+ if (claimsSet != null && !claimsSet.isEmpty()) {
+ pushedAuthorizationRequestContextLookupStrategy.apply(profileRequestContext)
+ .setClaimsSet(CollectionSupport.copyToMap(claimsSet));
+ log.debug("{} Pushed authorization request successfully deserialized and stored", getLogPrefix());
+ return;
+ }
+ }
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java
index 141cbe11..cb3916f8 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java
@@ -17,6 +17,8 @@ package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
import java.net.URI;
import java.text.ParseException;
import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -34,6 +36,8 @@ import com.nimbusds.oauth2.sdk.PushedAuthorizationRequest;
import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.oidc.profile.config.navigate.PushedAuthorizationRequestUriClaimsSetManipulationStrategyLookupFunction;
import net.shibboleth.oidc.profile.config.navigate.PushedAuthorizationRequestUriLifetimeLookupFunction;
@@ -67,6 +71,9 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
@Nonnull private Map<String, BiFunction<ProfileRequestContext,Map<String,Object>,URI>>
requestUriClaimsSetSerializationStrategies;
+ /** The strategy used to locate IDs for the selected trust chain. */
+ @Nonnull private Function<ProfileRequestContext,List<String>> selectedTrustChainIDsLookupStrategy;
+
/** The request URI type to use. */
@Nullable private String requestUriType;
@@ -88,6 +95,7 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
requestUriClaimsSetManipulationStrategyLookupStrategy =
new PushedAuthorizationRequestUriClaimsSetManipulationStrategyLookupFunction();
requestUriClaimsSetSerializationStrategies = CollectionSupport.emptyMap();
+ selectedTrustChainIDsLookupStrategy = new DefaultSelectedTrustChainIDsLookupStrategy();
}
/**
@@ -102,6 +110,19 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
"Request URI serialization strategies cannot be null");
}
+ /**
+ * Set the lookup strategy used to locate IDs for the selected trust chain.
+ *
+ * @param strategy What to set
+ */
+ public void setSelectedTrustChainIDsLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, List<String>> strategy) {
+ checkSetterPreconditions();
+
+ selectedTrustChainIDsLookupStrategy = Constraint.isNotNull(strategy,
+ "SelectedTrustChainIDsLookupStrategy lookup strategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -185,14 +206,7 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
final OIDCAuthenticationResponseContext oidcContext = getOidcResponseContext();
assert oidcContext != null;
- final Map<String, Object> claimsSet;
- final JWT requestObject = oidcContext.getRequestObject();
- if (requestObject == null) {
- assert requestMessage != null;
- claimsSet = requestMessage.getAuthorizationRequest().toJWTClaimsSet().getClaims();
- } else {
- claimsSet = requestObject.getJWTClaimsSet().getClaims();
- }
+ final Map<String, Object> claimsSet = buildClaimsSet(profileRequestContext, oidcContext.getRequestObject());
if (manipulationStrategy != null) {
log.debug("{} Manipulation strategy has been set, applying it to the claims set {}", getLogPrefix(),
@@ -210,7 +224,38 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
} else {
log.debug("{} No manipulation strategy configured", getLogPrefix());
}
- assert claimsSet != null;
return claimsSet;
}
+
+ /**
+ * Build the claims set by exploiting the contents of the pushed authorization request form parameters or request
+ * object. If the RP was automatically registered, the trust chain is stored to the claims set via key
+ * {@link TokenClaimsSet#KEY_AUTO_REGISTERED_TRUST_CHAIN}. Otherwise the claim is guaranteed to not exist in the
+ * claims set.
+ *
+ * @param profileRequestContext the profile request context to operate on
+ * @param requestObject the request object to be exploited if set
+ * @return the claims set
+ * @throws ParseException if the request object cannot be parsed into the claims set
+ */
+ @Nonnull protected Map<String,Object> buildClaimsSet(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nullable final JWT requestObject) throws ParseException {
+ final Map<String, Object> claimsSet;
+ if (requestObject == null) {
+ assert requestMessage != null;
+ claimsSet = new HashMap<>(requestMessage.getAuthorizationRequest().toJWTClaimsSet().getClaims());
+ } else {
+ claimsSet = new HashMap<>(requestObject.getJWTClaimsSet().getClaims());
+ }
+ if (claimsSet.containsKey(TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN)) {
+ claimsSet.remove(TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN);
+ log.warn("{} Removed {} from the request", getLogPrefix(), TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN);
+ }
+ final List<String> selectedTrustChain = selectedTrustChainIDsLookupStrategy.apply(profileRequestContext);
+ if (selectedTrustChain != null && !selectedTrustChain.isEmpty()) {
+ claimsSet.put(TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN, selectedTrustChain);
+ log.debug("{} Included selected trust chain {} the claims set", getLogPrefix(), selectedTrustChain);
+ }
+ return CollectionSupport.copyToMap(claimsSet);
+ }
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
index c6153249..eddd45b4 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
@@ -18,10 +18,10 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
-import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
-import java.util.function.BiFunction;
+import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
@@ -34,10 +34,12 @@ import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.messaging.encoder.AbstractMessageEncoder;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.opensaml.security.httpclient.HttpClientSecurityParameters;
import org.opensaml.security.httpclient.HttpClientSecuritySupport;
import org.slf4j.Logger;
@@ -53,11 +55,11 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
import net.shibboleth.idp.plugin.oidc.op.encoding.impl.ResponseUtil;
import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2PushedAuthorizationRequestContext;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
import net.shibboleth.oidc.profile.core.OidcEventIds;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
@@ -90,9 +92,9 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
/** Object mapper used for pretty-printing JWT contents. */
@NonnullAfterInit private ObjectMapper objectMapper;
- /** List of deserializers for OP-issued request_uri values. */
- @Nonnull private List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>>
- pushedAuthorizationRequestUriDeserializers;
+ /** Lookup strategy for pushed authorization request context. */
+ @Nonnull private Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext>
+ pushedAuthorizationRequestContextLookupStrategy;
/** Whether to require pushed authorization request to be used. */
private boolean requirePushedAuthorization = false;
@@ -101,7 +103,11 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
* Constructor.
*/
public SetRequestObjectToResponseContext() {
- pushedAuthorizationRequestUriDeserializers = CollectionSupport.emptyList();
+ final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> parcls =
+ new ChildContextLookup<>(OAuth2PushedAuthorizationRequestContext.class, true).compose(
+ new InboundMessageContextLookup());
+ assert parcls != null;
+ pushedAuthorizationRequestContextLookupStrategy = parcls;
}
/**
@@ -111,6 +117,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
* client to use
*/
public void setHttpClient(@Nonnull final HttpClient client) {
+ checkSetterPreconditions();
httpClient = Constraint.isNotNull(client, "HttpClient cannot be null");
}
@@ -121,6 +128,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
* the new client security parameters
*/
public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) {
+ checkSetterPreconditions();
httpClientSecurityParameters = params;
}
@@ -130,6 +138,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
* @param predicate the predicate for enforcing the use of request objects
*/
public void setRequestObjectEnforcedPredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
+ checkSetterPreconditions();
requestObjectEnforcedPredicate = Constraint.isNotNull(predicate,
"Request object enforced predicate annot be null");
}
@@ -143,6 +152,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
*/
public void setPushedAuthorizationRequestEnforcedPredicate(
@Nonnull final Predicate<ProfileRequestContext> predicate) {
+ checkSetterPreconditions();
pushedAuthorizationRequestEnforcedPredicate = Constraint.isNotNull(predicate,
"Pushed authorization request enforced predicate annot be null");
}
@@ -160,17 +170,17 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
}
/**
- * Set the list of deserializers for OP-issued request_uri values.
+ * Set the lookup strategy for pushed authorization request context.
*
- * @param deserializers What to set.
+ * @param strategy the lookup strategy for pushed authorization request context
*
- * @since 4.2.0
+ * @since 4.3.0
*/
- public void setPushedAuthorizationRequestUriDeserializers(
- @Nonnull final List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>> deserializers) {
+ public void setPushedAuthorizationRequestContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> strategy) {
checkSetterPreconditions();
- pushedAuthorizationRequestUriDeserializers = Constraint.isNotNull(deserializers,
- "List of request_uri deserializers cannot be null");
+ pushedAuthorizationRequestContextLookupStrategy = Constraint.isNotNull(strategy,
+ "Pushed authorization request context lookup strategy cannot be null");
}
/**
@@ -286,13 +296,11 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
}
if (!authorized) {
- for (final BiFunction<ProfileRequestContext,URI,Map<String,Object>> deserializer :
- pushedAuthorizationRequestUriDeserializers) {
- final Map<String,Object> claimsSet = deserializer.apply(profileRequestContext,
- authorizationRequest.getRequestURI());
- if (claimsSet == null) {
- continue;
- }
+ final Map<String,Object> claimsSet =
+ Optional.ofNullable(pushedAuthorizationRequestContextLookupStrategy.apply(profileRequestContext))
+ .map(ctx -> ctx.getClaimsSet())
+ .orElse(null);
+ if (claimsSet != null && !claimsSet.isEmpty()) {
try {
final JWTClaimsSet jwtClaimsSet = JWTClaimsSet.parse(claimsSet);
oidcResponseContext.setRequestObject(new PlainJWT(jwtClaimsSet));
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy.java
new file mode 100644
index 00000000..34e4f74b
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy.java
@@ -0,0 +1,52 @@
+/*
+ * 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.Objects;
+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.oauth2.messaging.context.OAuth2PushedAuthorizationRequestContext;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+
+/**
+ * Default strategy for looking up the entity IDs of the previously selected trust chain to be used in the authorization
+ * endpoint. The selected trust chain is fetched via {@link OAuth2PushedAuthorizationRequestContext#getClaimsSet()}.
+ */
+public class DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy
+ implements Function<ProfileRequestContext,List<String>> {
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public List<String> apply(@Nullable final ProfileRequestContext input) {
+ final List<?> result = Optional.ofNullable(input)
+ .filter(prc -> prc.getInboundMessageContext() != null)
+ .map(prc -> prc.ensureInboundMessageContext())
+ .filter(msgCtx -> msgCtx.containsSubcontext(OAuth2PushedAuthorizationRequestContext.class))
+ .map(msgCtx -> msgCtx.ensureSubcontext(OAuth2PushedAuthorizationRequestContext.class))
+ .map(parCtx -> parCtx.getClaimsSet())
+ .filter(Objects::nonNull)
+ .map(claimsSet -> claimsSet.get(TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN))
+ .filter(List.class::isInstance)
+ .map(List.class::cast)
+ .orElse(null);
+ return result == null ? null : result.stream().filter(Objects::nonNull).map(obj -> obj.toString()).toList();
+ }
+}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
index 0f69d394..1707e1ef 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
@@ -150,7 +150,28 @@
</property>
</bean>
- <bean id="UseRequestObjectPredicate" class="net.shibboleth.oidc.profile.config.logic.UseRequestObjectPredicate" />
+ <bean id="UseRequestObjectPredicate" parent="shibboleth.Conditions.OR">
+ <constructor-arg>
+ <bean class="net.shibboleth.oidc.profile.config.logic.UseRequestObjectPredicate" />
+ </constructor-arg>
+ <constructor-arg>
+ <bean parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <bean id="NoneAuthenticationCondition" parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage().getClientAuthentication() == null or T(com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod).NONE.equals(#input.ensureInboundMessageContext().getMessage().getClientAuthentication().getMethod())" />
+ </constructor-arg>
+ <constructor-arg>
+ <bean id="IsAutomaticallyRegistered" parent="shibboleth.Conditions.Expression"
+ c:expression="#custom.apply(#input) != null">
+ <property name="customObject">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy" />
+ </property>
+ </bean>
+ </constructor-arg>
+ </bean>
+ </constructor-arg>
+ </bean>
+
<bean id="ValidateRequestObjectSignature" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
scope="prototype" c:executionDirection="INBOUND">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
index 83a13584..c6cce2eb 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
@@ -82,4 +82,7 @@
</property>
</bean>
+ <bean id="shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultPreSelectedTrustChainIDsLookupStrategy" />
+
</beans>
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 7bc77ec5..9a09cce2 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -116,6 +116,10 @@
</property>
</bean>
+ <bean id="DeserializedPushedRequest"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.DeserializedPushedRequest" scope="prototype"
+ p:pushedAuthorizationRequestUriDeserializers-ref="#{'%{idp.oauth2.par.deserializationStrategies:shibboleth.oidc.DefaultPushedAuthorizationRequestUriDeserializers}'.trim()}"/>
+
<bean id="InitializeOutboundMessageContext"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeOutboundAuthenticationResponseMessageContext"
scope="prototype" />
@@ -148,8 +152,7 @@
p:httpClient="#{getObject('shibboleth.oidc.NonBrowser.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
p:httpClientSecurityParameters="#{getObject('shibboleth.oidc.NonBrowser.HttpClientSecurityParameters')}"
p:pushedAuthorizationRequestEnforcedPredicate-ref="RequirePushedAuthorizationRequestPredicate"
- p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}"
- p:pushedAuthorizationRequestUriDeserializers-ref="#{'%{idp.oauth2.par.deserializationStrategies:shibboleth.oidc.DefaultPushedAuthorizationRequestUriDeserializers}'.trim()}">
+ p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}">
<property name="requestObjectEnforcedPredicate">
<bean parent="shibboleth.Conditions.OR">
<constructor-arg>
@@ -336,23 +339,43 @@
</bean>
</constructor-arg>
<constructor-arg>
- <bean parent="shibboleth.Conditions.AND">
+ <bean parent="shibboleth.Conditions.OR">
<constructor-arg>
<bean parent="shibboleth.Conditions.AND">
<constructor-arg>
- <bean id="RequestObjectInvolved" parent="shibboleth.Conditions.Expression"
- c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getRequestObject() != null" />
+ <bean parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <bean id="RequestObjectInvolved" parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getRequestObject() != null" />
+ </constructor-arg>
+ <constructor-arg>
+ <bean id="RequestObjectNotFromPar" parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).isRequestObjectFromPar() == false" />
+ </constructor-arg>
+ </bean>
</constructor-arg>
<constructor-arg>
- <bean id="RequestObjectNotFromPar" parent="shibboleth.Conditions.Expression"
- c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).isRequestObjectFromPar() == false" />
+ <bean parent="shibboleth.Conditions.NOT">
+ <constructor-arg>
+ <ref bean="ScopeContainsOpenid" />
+ </constructor-arg>
+ </bean>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
- <bean parent="shibboleth.Conditions.NOT">
+ <bean parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <bean id="RequestObjectNotFromPar" parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).isRequestObjectFromPar() == false" />
+ </constructor-arg>
<constructor-arg>
- <ref bean="ScopeContainsOpenid" />
+ <bean id="IsAutomaticallyRegistered" parent="shibboleth.Conditions.Expression"
+ c:expression="#custom.apply(#input) != null">
+ <property name="customObject">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy" />
+ </property>
+ </bean>
</constructor-arg>
</bean>
</constructor-arg>
@@ -1146,6 +1169,17 @@
</constructor-arg>
</bean>
- <alias alias="AutomaticRegistrationCondition" name="%{idp.oidfed.authorize.automaticRegistrationCondition:shibboleth.Conditions.FALSE}" />
+ <bean id="AutomaticRegistrationCondition" parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <ref bean="%{idp.oidfed.authorize.automaticRegistrationCondition:shibboleth.Conditions.FALSE}"/>
+ </constructor-arg>
+ <constructor-arg>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage() instanceof T(com.nimbusds.oauth2.sdk.AuthorizationRequest) and #input.ensureInboundMessageContext().getMessage().specifiesRequestObject()" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy" />
</beans>
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index 4ffc9741..56e3497c 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -31,6 +31,7 @@
<action-state id="PostDecodeMessage">
<evaluate expression="PostDecodePopulateAuditContext" />
+ <evaluate expression="DeserializedPushedRequest" />
<evaluate expression="'proceed'" />
<!-- DoMetadataLookup is expected to proceed to SelectConfiguration -->
<transition on="proceed" to="DoMetadataLookup" />
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 2a27ed5b..ac639f57 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
@@ -87,7 +87,8 @@
p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
- p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"/>
+ p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"
+ p:preSelectedTrustChainIdsLookupStrategy="#{getObject('shibboleth.oidfed.PreSelectedTrustChainIDsLookupStrategy') ?: getObject('shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy')}"/>
<bean id="DefaultMetadataPolicyEnforcer"
class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer" />
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list