[java-idp-plugin-oidc-rp] branch main updated: Add UserInfo response validation and id_token merge
Phil Smart
philip.smart at jisc.ac.uk
Tue Feb 15 09:56:42 UTC 2022
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-oidc-rp.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=faad89ffc5af13cbed592210325e4fa8dd8595f8
The following commit(s) were added to refs/heads/main by this push:
new faad89f Add UserInfo response validation and id_token merge
faad89f is described below
commit faad89ffc5af13cbed592210325e4fa8dd8595f8
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue Feb 15 09:56:36 2022 +0000
Add UserInfo response validation and id_token merge
---
.../impl/DefaultUserInfoResponseDecoder.java | 27 +++--
.../oidc/rp/impl/DefaultClaimMergingStrategy.java | 13 +-
.../rp/impl/DefaultClaimSanitizationStrategy.java | 65 ++++++++++
.../rp/impl/MergeUserInfoAndIDTokenClaims.java | 75 +++++++++---
.../oidc/rp/impl/ValidateOIDCAuthentication.java | 26 ++--
.../authn/oidc/rp/impl/ValidateUserInfoClaims.java | 2 +-
.../oidc-relying-party-authn-beans.xml | 2 +-
.../oidc-relying-party-authn-flow.xml | 4 +-
.../rp/impl/DefaultClaimMergingStrategyTest.java | 28 ++++-
.../impl/DefaultClaimSanatizationStrategyTest.java | 67 ++++++++++
.../rp/impl/ExtractIDTokenFromResponseTest.java | 25 ++--
.../rp/impl/MergeUserInfoAndIDTokenClaimsTest.java | 126 +++++++++++++++++++
.../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java | 135 ++++++++++++++++++++-
13 files changed, 533 insertions(+), 62 deletions(-)
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java
index 260a1c8..3b2cd1f 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultUserInfoResponseDecoder.java
@@ -31,15 +31,17 @@ import org.springframework.util.MimeType;
import com.fasterxml.jackson.core.type.TypeReference;
import com.nimbusds.jose.util.IOUtils;
+import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;
+import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.JWTUserInfoResponse;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.PlainUserInfoResponse;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
-/** Response decoder for UserInfo responses.*/
+/** Response decoder for UserInfo responses. Supports both plain JSON Object and JWT responses.*/
public class DefaultUserInfoResponseDecoder extends AbstractJSONResponseDecoderFunction<UserInfoResponse> {
/** The application/jwt media type.*/
@@ -48,6 +50,7 @@ public class DefaultUserInfoResponseDecoder extends AbstractJSONResponseDecoderF
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(DefaultUserInfoResponseDecoder.class);
+ // CheckStyle: CyclomaticComplexity OFF
@Override
public UserInfoResponse apply(@Nonnull final HttpResponse httpResponse) {
@@ -78,19 +81,27 @@ public class DefaultUserInfoResponseDecoder extends AbstractJSONResponseDecoderF
// Is a JWT type or plain JSON object
if (APPLICATION_JWT.compareTo(MimeType.valueOf(contentType.getMimeType())) == 0) {
+
final JWT parsedJwt = JWTParser.parse(content);
- log.trace("UserInfo response parsed a JWT type");
+
+ if (log.isTraceEnabled()) {
+ log.trace("UserInfo response decoder parsed a {} JWT type",
+ parsedJwt instanceof SignedJWT ? "Signed" :
+ (parsedJwt instanceof EncryptedJWT ? "Encrypted" : "plain"));
+ }
+
return new JWTUserInfoResponse(parsedJwt);
} else if (MediaType.APPLICATION_JSON.compareTo(MimeType.valueOf(contentType.getMimeType())) == 0){
- final Map<String, Object> claims = getObjectMapper().readValue(content,
- new TypeReference<Map<String, Object>>() {});
+
+ final Map<String, Object> claims = getObjectMapper().readValue(
+ content, new TypeReference<Map<String, Object>>() {});
final ClaimsSet claimsSet = new ClaimsSet();
claimsSet.putAll(claims);
- log.debug("UserInfo endpoint returned claims for '{}'", claimsSet.getStringClaim("sub"));
+
if (log.isTraceEnabled()) {
- log.trace("UserInfo endpoint claims for '{}' are '{}'", claimsSet.getStringClaim("sub"),
- claimsSet.toJSONString());
+ log.trace("UserInfo response decoder parsed a plain JSON Object for subject '{}'",
+ claimsSet.getStringClaim("sub"));
}
return new PlainUserInfoResponse(claimsSet);
}
@@ -102,5 +113,5 @@ public class DefaultUserInfoResponseDecoder extends AbstractJSONResponseDecoderF
return null;
}
-
+ // CheckStyle: CyclomaticComplexity ON
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategy.java
index a72cc7b..70f5797 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategy.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategy.java
@@ -20,6 +20,7 @@ package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
+import java.util.function.BinaryOperator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -40,17 +41,17 @@ import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
* </ol>
*/
//TODO maybe this should filter claims as well e.g. exp etc. Security check this logic.
-public class DefaultClaimMergingStrategy implements BiFunction<ClaimsSet, JWTClaimsSet, ClaimsSet> {
+public class DefaultClaimMergingStrategy implements BinaryOperator<ClaimsSet> {
/** Class logger.*/
@Nonnull private final Logger log = LoggerFactory.getLogger(DefaultClaimMergingStrategy.class);
@Override
- @Nonnull public ClaimsSet apply(@Nullable final ClaimsSet userInfo, @Nullable final JWTClaimsSet idToken) {
+ @Nonnull public ClaimsSet apply(@Nullable final ClaimsSet userInfo, @Nullable final ClaimsSet idToken) {
if (userInfo == null && idToken != null) {
final ClaimsSet singleSet = new ClaimsSet();
- singleSet.putAll(idToken.getClaims());
+ singleSet.putAll(idToken.toJSONObject());
return singleSet;
}
if (userInfo != null && idToken == null) {
@@ -61,11 +62,9 @@ public class DefaultClaimMergingStrategy implements BiFunction<ClaimsSet, JWTCla
if (userInfo == null && idToken == null) {
// return empty claimsset
return new ClaimsSet();
- }
-
-
+ }
- final Map<String, Object> idTokenAsMap = idToken.getClaims();
+ final Map<String, Object> idTokenAsMap = idToken.toJSONObject();
// Treat JSONObject as the base map representation.
final Map<String, Object> userInfoClaimsAsMap = userInfo.toJSONObject();
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimSanitizationStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimSanitizationStrategy.java
new file mode 100644
index 0000000..d2f7f79
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimSanitizationStrategy.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.authn.oidc.rp.impl;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.oidc.security.jwt.claims.impl.IDTokenClaims;
+import net.shibboleth.oidc.security.jwt.claims.impl.JWTClaims;
+
+/**
+ * Produce a claims set from the JWT claims set without the validation claims, leaving the identity,
+ * authorization, and misc. claims.
+ */
+public class DefaultClaimSanitizationStrategy implements UnaryOperator<ClaimsSet> {
+
+ /** The set of validation claims to filter out of the input claims.*/
+ @Nonnull private final Set<String> validationClaims;
+
+ /** Constructor.*/
+ public DefaultClaimSanitizationStrategy() {
+ validationClaims = Set.of(IDTokenClaims.AUTHORIZED_PARTY.getClaimName(),
+ IDTokenClaims.NONCE.getClaimName(),
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ IDTokenClaims.AUTHENTICATION_CONTEXT_CLASS_REFERENCE.getClaimName(),
+ IDTokenClaims.AUTHENTICATION_METHODS_REFERENCES.getClaimName(),
+ JWTClaims.ISSUER_CLAIM.getClaimName(),
+ JWTClaims.ISSUED_AT_CLAIM.getClaimName(),
+ JWTClaims.AUDIENCE_CLAIM.getClaimName(),
+ JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName());
+ }
+
+ @Override
+ public ClaimsSet apply(@Nonnull final ClaimsSet jwtClaims) {
+ final ClaimsSet sanitizedClaims = new ClaimsSet();
+ final Map<String, Object> filteredMap = jwtClaims.toJSONObject().entrySet()
+ .stream()
+ .filter(c -> !validationClaims.contains(c.getKey()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ sanitizedClaims.putAll(filteredMap);
+ return sanitizedClaims;
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaims.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaims.java
index cd8c549..222f86e 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaims.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaims.java
@@ -18,8 +18,9 @@
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
import java.text.ParseException;
-import java.util.function.BiFunction;
+import java.util.function.BinaryOperator;
import java.util.function.Function;
+import java.util.function.UnaryOperator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -39,13 +40,14 @@ import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
/**
- * Merge the claims in the id_token with the claims from the UserInfo response.
+ * Merge the claims in the id_token with the claims from the UserInfo response. Before merge,
+ * both id_token and UserInfo claim sets are sanitized by a replaceable strategy. For example,
+ * by default to remove 'validation claims' that should not be exposed further by the system.
+ * This can be turned off by setting a no-op sanitizer, or setting
*/
//TODO similar too ValidateUserInfoClaims, do we need to extend OIDC action
public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAction {
@@ -66,7 +68,14 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
endUserClaimsContextLookupStrategy;
/** The strategy used to merge UserInfo claims with id_token claims.*/
- @NonnullAfterInit private BiFunction<ClaimsSet, JWTClaimsSet, ClaimsSet> claimMergingStrategy;
+ @Nonnull private BinaryOperator<ClaimsSet> claimMergingStrategy;
+
+ /**
+ * The strategy used to sanitize claims in an input claimset. By default, produces a set of
+ * claims without the validation claims (e.g. nonce, exp), but leaving the identity, authorization and
+ * misc claims.
+ */
+ @Nonnull private UnaryOperator<ClaimsSet> claimSanitizationStrategy;
/** The stashed user info claims.*/
@Nullable private ClaimsSet userInfoClaims;
@@ -89,16 +98,9 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
new InboundMessageContextLookup());
claimMergingStrategy = new DefaultClaimMergingStrategy();
+ claimSanitizationStrategy = new DefaultClaimSanitizationStrategy();
}
-
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (claimMergingStrategy == null) {
- throw new ComponentInitializationException("ClaimMergingStrategy cannot be null");
- }
- }
+
/**
* Set the strategy used to merge UserInfo claims with id_token claims.
@@ -106,13 +108,43 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
* @param strategy the strategy to use.
*/
public void setClaimMergingStrategy(
- @Nonnull final BiFunction<ClaimsSet, JWTClaimsSet, ClaimsSet> strategy) {
+ @Nonnull final BinaryOperator<ClaimsSet> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
claimMergingStrategy = Constraint.isNotNull(strategy,
"ClaimMergingStrategy cannot be null");
}
+ /**
+ * Set the strategy used to sanitize claims from both the id_token and UserInfo
+ * claims set to produce a clean claims set.
+ *
+ * @param strategy the strategy to use.
+ */
+ public void setClaimSanitizationStrategy(
+ @Nonnull final UnaryOperator<ClaimsSet> strategy) {
+ claimSanitizationStrategy = Constraint.isNotNull(strategy,
+ "ClaimSanatizationStrategy cannot be null");
+ }
+
+ /**
+ * Set whether to enable claim sanitization. If true, whatever claimSanatizationStrategy
+ * is set is used. If false, a no-op strategy is created which just returns a new claims set
+ * based on the same claims that exist in the input claims set. By default, claims sanitization
+ * uses the {@link DefaultClaimSanitizationStrategy}.
+ *
+ * @param enable enable or disable claims sanitization
+ */
+ public void setEnableClaimSanitizationStrategy(final boolean enable) {
+ if (!enable) {
+ claimSanitizationStrategy = c -> {
+ final ClaimsSet claims = new ClaimsSet();
+ claims.putAll(c);
+ return claims;
+ };
+ }
+ }
+
/**
* Set the strategy used to lookup a {@link EndUserClaimsContext}.
*
@@ -121,6 +153,7 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
public void setEndUserClaimsContextLookupStrategy(
@Nonnull final Function<ProfileRequestContext, EndUserClaimsContext> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
endUserClaimsContextLookupStrategy = Constraint.isNotNull(strategy,
"EndUserClaimsContextLookupStrategy cannot be null");
@@ -135,6 +168,7 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
public void setTokenResponseContextLookupStrategy(
@Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
"TokenResponseContext lookup strategy cannot be null");
@@ -148,6 +182,7 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
public void setUserInfoResponseContextLookupStrategy(
@Nonnull final Function<ProfileRequestContext, UserInfoResponseContext> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
userInfoResponseContextLookupStrategy = Constraint.isNotNull(strategy,
"UserInfoResponseContext lookup strategy cannot be null");
@@ -204,13 +239,19 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
log.trace("{} Merging UserInfo and id_token claims", getLogPrefix());
- final ClaimsSet mergedClaims = claimMergingStrategy.apply(userInfoClaims, idTokenClaims);
+ final ClaimsSet idToken = new ClaimsSet();
+ idToken.putAll(idTokenClaims.toJSONObject());
+
+ final ClaimsSet sanitizedUserInfoClaims = claimSanitizationStrategy.apply(userInfoClaims);
+ final ClaimsSet sanitizedIdTokenClaims = claimSanitizationStrategy.apply(idToken);
+
+ final ClaimsSet mergedClaims = claimMergingStrategy.apply(sanitizedUserInfoClaims, sanitizedIdTokenClaims);
// Add to end user claims context
endUserClaimsContextLookupStrategy.apply(profileRequestContext).setEndUserClaims(mergedClaims);
if (log.isTraceEnabled()) {
- log.trace("{} Merged UserInfo and id_token claims to produce '{}'",
+ log.trace("{} Merged UserInfo and id_token claims to produce a claims set containing '{}'",
getLogPrefix(), mergedClaims.toJSONString());
}
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
index 67d8fb0..aad5c6e 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
@@ -17,7 +17,6 @@
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-import java.text.ParseException;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
@@ -36,13 +35,13 @@ import org.slf4j.LoggerFactory;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
-import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
import net.minidev.json.JSONObject;
import net.shibboleth.idp.attribute.AttributeDecodingException;
import net.shibboleth.idp.attribute.AttributeEncodingException;
import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.filter.AttributeFilter;
import net.shibboleth.idp.attribute.transcoding.AttributeTranscoder;
import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
import net.shibboleth.idp.attribute.transcoding.TranscoderSupport;
@@ -53,7 +52,6 @@ import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
import net.shibboleth.idp.authn.principal.UsernamePrincipal;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
@@ -91,23 +89,20 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(ValidateOIDCAuthentication.class);
-
- /** Avoid creating multiple principals. */
- private boolean avoidMultiplePrincipal;
-
- /** the subject received from id token. */
- @Nullable private String oidcSubject;
/** Transcoder registry service object. */
@NonnullAfterInit private ReloadableService<AttributeTranscoderRegistry> transcoderRegistry;
+ /** Service used to get the engine used to filter attributes. */
+ @Nullable private ReloadableService<AttributeFilter> attributeFilterService;
+
/** Strategy used to look up a {@link RelyingPartyContext} for configuration options. */
@Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
/** Store off profile config. */
@Nullable private OIDCAuthorizationConfiguration profileConfiguration;
- /** Claims Set to extract and decode claims from. */
+ /** End-user claims Set to extract and transcode claims from. */
@Nullable private ClaimsSet claimsSet;
/** Strategy used to look up the {@link EndUserClaimsContext} to set the parameters for. */
@@ -124,6 +119,17 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
new InboundMessageContextLookup());
}
+ /**
+ * Sets the filter service to use for inbound attributes.
+ *
+ * @param filterService optional filter service for inbound attributes
+ */
+ public void setAttributeFilter(@Nullable final ReloadableService<AttributeFilter> filterService) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ attributeFilterService = filterService;
+ }
+
/**
* Sets the registry of transcoding rules to apply to encode attributes.
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoClaims.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoClaims.java
index 5254b4d..af84011 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoClaims.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoClaims.java
@@ -146,7 +146,7 @@ public class ValidateUserInfoClaims extends AbstractOIDCAuthenticationAction {
final UserInfoResponse response = userInfoCtx.getUserInfo();
if (!response.isClaimsSetAvailable()) {
- log.debug("{} UserInfo claims are not available, check response not still encrypted", getLogPrefix());
+ log.debug("{} UserInfo claims are not available, check response is not still encrypted", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_USERINFO_CLAIMS);
return;
}
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
index 95e9857..ff70537 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
@@ -438,7 +438,7 @@
<!-- UserInfo response JWT validation -->
- <bean id="ValidateUserInfoTokenClaims" scope="prototype"
+ <bean id="ValidateUserInfoToken" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateTokenClaims"
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
index f200c46..90af8f1 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
@@ -154,14 +154,14 @@
<action-state id="ValidateSignedUserInfoJWT">
<!-- <evaluate expression="PopulateTokenSignatureSigningParameters" /> -->
- <evaluate expression="ValidateUserInfoTokenClaims" />
+ <evaluate expression="ValidateUserInfoToken" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="ValidateUserInfoClaimsSet" />
</action-state>
<action-state id="DecryptUserInfoJWT">
<!-- <evaluate expression="PopulateTokenEncryptionParameters" /> -->
- <evaluate expression="ValidateUserInfoTokenClaims" />
+ <evaluate expression="ValidateUserInfoToken" /> <!-- Will die if not decrypted properly first -->
<evaluate expression="'proceed'" />
<transition on="proceed" to="ValidateUserInfoClaimsSet" />
</action-state>
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategyTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategyTest.java
index 1ade5f1..1b80a6a 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategyTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimMergingStrategyTest.java
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.authn.oidc.rp.impl;
import static org.junit.Assert.assertEquals;
@@ -20,7 +37,7 @@ public class DefaultClaimMergingStrategyTest {
/** Mock response from the UserInfo endpoint.*/
@Nonnull @NotEmpty
- protected final String USERINFO_RESPONSE ="{\n"
+ private final String USERINFO_RESPONSE ="{\n"
+ " \"sub\": \"jdoe\",\n"
+ " \"website\": \"https://openid.net/\",\n"
+ " \"zoneinfo\": \"America/Los_Angeles\",\n"
@@ -41,10 +58,12 @@ public class DefaultClaimMergingStrategyTest {
final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
final ClaimsSet userInfo = new ClaimsSet((JSONObject)parser.parse(USERINFO_RESPONSE));
- final JWTClaimsSet idTokenClaims = new JWTClaimsSet.Builder().subject("jdoe")
+ final JWTClaimsSet idToken = new JWTClaimsSet.Builder().subject("jdoe")
.claim("given_name", "FromIdToken")
.claim("nickname", "FromIdToken")
.build();
+ final ClaimsSet idTokenClaims = new ClaimsSet();
+ idTokenClaims.putAll(idToken.getClaims());
final DefaultClaimMergingStrategy strategy = new DefaultClaimMergingStrategy();
final ClaimsSet merged = strategy.apply(userInfo, idTokenClaims);
@@ -62,11 +81,12 @@ public class DefaultClaimMergingStrategyTest {
@Test
public void testMergeNullUserInfo() throws ParseException {
- final JWTClaimsSet idTokenClaims = new JWTClaimsSet.Builder().subject("jdoe")
+ final JWTClaimsSet idToken = new JWTClaimsSet.Builder().subject("jdoe")
.claim("given_name", "FromIdToken")
.claim("nickname", "FromIdToken")
.build();
-
+ final ClaimsSet idTokenClaims = new ClaimsSet();
+ idTokenClaims.putAll(idToken.getClaims());
final DefaultClaimMergingStrategy strategy = new DefaultClaimMergingStrategy();
final ClaimsSet merged = strategy.apply(null, idTokenClaims);
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimSanatizationStrategyTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimSanatizationStrategyTest.java
new file mode 100644
index 0000000..40b00cc
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultClaimSanatizationStrategyTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.authn.oidc.rp.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.oidc.security.jwt.claims.impl.IDTokenClaims;
+import net.shibboleth.oidc.security.jwt.claims.impl.JWTClaims;
+
+
+/** Tests for DefaultClaimSanatizationStrategy.*/
+public class DefaultClaimSanatizationStrategyTest {
+
+ @Test
+ public void testRemoveValidationClaims() {
+ final DefaultClaimSanitizationStrategy strategy = new DefaultClaimSanitizationStrategy();
+
+ final JWTClaimsSet idToken = new JWTClaimsSet.Builder()
+ .subject("joe")
+ .issuer("https://op.example.com")
+ .audience(List.of("https://rp.example.com"))
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plus(Duration.ofSeconds(10))))
+ .claim(IDTokenClaims.NONCE.getClaimName(), "noncevalue")
+ .claim("given_name", "Joe")
+ .build();
+
+ final ClaimsSet idTokenClaims = new ClaimsSet();
+ idTokenClaims.putAll(idToken.getClaims());
+ final ClaimsSet sanClaims = strategy.apply(idTokenClaims);
+ assertNull(sanClaims.getAudience());
+ assertNull(sanClaims.getIssuer());
+ assertNull(sanClaims.getClaim(IDTokenClaims.NONCE.getClaimName()));
+ assertNull(sanClaims.getClaim(JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName()));
+ assertNull(sanClaims.getClaim(JWTClaims.ISSUED_AT_CLAIM.getClaimName()));
+
+ assertEquals(sanClaims.getClaim(JWTClaims.SUBJECT_CLAIM.getClaimName()),"joe");
+ assertEquals(sanClaims.getClaim("given_name"),"Joe");
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponseTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponseTest.java
index eb12fe0..5fad162 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponseTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponseTest.java
@@ -1,22 +1,31 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.authn.oidc.rp.impl;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
-import java.io.ByteArrayInputStream;
-import java.util.Map;
-
import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.messaging.context.navigate.ParentContextLookup;
import org.opensaml.profile.context.ProfileRequestContext;
import org.springframework.webflow.execution.Event;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.nimbusds.jose.shaded.json.parser.JSONParser;
-
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaimsTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaimsTest.java
new file mode 100644
index 0000000..f4ea4db
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaimsTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.authn.oidc.rp.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.ParentContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.PlainUserInfoResponse;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.oidc.security.jwt.claims.impl.JWTClaims;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Tests for {@link MergeUserInfoAndIDTokenClaims}.*/
+public class MergeUserInfoAndIDTokenClaimsTest extends AbstractOIDCTest {
+
+ /** Action to test.*/
+ private MergeUserInfoAndIDTokenClaims action;
+
+
+ @BeforeMethod
+ public void setup() throws Exception {
+ super.setup();
+ action = new MergeUserInfoAndIDTokenClaims();
+
+ final AccessTokenResponseContext trc = new AccessTokenResponseContext();
+ final PlainJWT jwt = new PlainJWT(new JWTClaimsSet.Builder()
+ .issuer("https://op.example.com")
+ .audience(List.of("https://rp.example.com"))
+ .subject("jdoe")
+ .claim("nonce", "abadnonce")
+ .claim("azp", "https://rp.example.com")
+ .claim("name","jdoe")
+ .expirationTime(Date.from(Instant.now().plusSeconds(120)))
+ .build());
+ trc.setIdToken(jwt);
+ prc.getInboundMessageContext().addSubcontext(trc);
+
+ final ClaimsSet claims = new ClaimsSet();
+ claims.putAll(Map.of("sub","jdoe","given_name","John","email","jdoe at example.com"));
+ final UserInfoResponse userInfoResponse = new PlainUserInfoResponse(claims);
+ final UserInfoResponseContext urc = new UserInfoResponseContext();
+ urc.setUserInfo(userInfoResponse);
+ prc.getInboundMessageContext().addSubcontext(urc);
+
+ action.setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
+
+ action.setProfileContextLookupStrategy(new ChildContextLookup<>(ProfileRequestContext.class).compose(
+ new ChildContextLookup<>(AuthenticationContext.class)
+ .compose(new WebflowRequestContextProfileRequestContextLookup())));
+ }
+
+ @Test
+ public void testSuccesfulMerge() throws ComponentInitializationException {
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ assertNotNull(prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims());
+ final ClaimsSet claims =
+ prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims();
+ assertEquals(claims.getClaim("name"),"jdoe");
+ assertEquals(claims.getClaim("sub"),"jdoe");
+ assertEquals(claims.getClaim("given_name"),"John");
+ assertEquals(claims.getClaim("email"),"jdoe at example.com");
+ assertNull(claims.getIssuer());
+ assertNull(claims.getAudience());
+ assertNull(claims.getDateClaim(JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName()));
+ }
+
+ @Test
+ public void testSuccesfulMergeNoSanitization() throws ComponentInitializationException {
+
+ action.setEnableClaimSanitizationStrategy(false);
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ assertNotNull(prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims());
+ final ClaimsSet claims =
+ prc.getInboundMessageContext().getSubcontext(EndUserClaimsContext.class).getEndUserClaims();
+ assertEquals(claims.getClaim("name"),"jdoe");
+ assertEquals(claims.getClaim("sub"),"jdoe");
+ assertEquals(claims.getClaim("given_name"),"John");
+ assertEquals(claims.getClaim("email"),"jdoe at example.com");
+ assertNotNull(claims.getIssuer());
+ assertNotNull(claims.getAudience());
+ assertNotNull(claims.getDateClaim(JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName()));
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
index a34bad7..3137d71 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
@@ -45,14 +45,25 @@ import org.springframework.webflow.engine.impl.FlowExecutionImpl;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.test.MockFlowBuilderContext;
+import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEHeader;
+import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.ParseException;
@@ -309,7 +320,15 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
return accessTokenSerialized;
}
- private String createSignedUserInfoJWTResponseJSON(final String issuer, final String audience)
+ /**
+ * Create a signed UserInfo response JWT.
+ *
+ * @param issuer the issuer
+ * @param audience the audience
+ * @return the signed JWT
+ * @throws JOSEException on error
+ */
+ private SignedJWT createSignedUserInfoJWTResponseJSON(final String issuer, final String audience)
throws JOSEException {
final var key = new ECKeyGenerator(Curve.P_256).keyID("123").generate();
@@ -328,7 +347,32 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
final var signedJWT = new SignedJWT(header, payload);
signedJWT.sign(new ECDSASigner(key.toECPrivateKey()));
- return signedJWT.serialize();
+ return signedJWT;
+ }
+
+ /**
+ * Create a signed and encrypted UserInfo response JWT.
+ *
+ * @param issuer the issuer
+ * @param audience the audience
+ * @return the signed JWT
+ * @throws JOSEException on error
+ */
+ private EncryptedJWT createSignedAndEncryptedUserInfoJWTResponseJSON(final String issuer, final String audience)
+ throws Exception {
+
+ final RSAKey keyRecipient = new RSAKeyGenerator(2048)
+ .keyID("2")
+ .keyUse(KeyUse.ENCRYPTION)
+ .generate();
+
+ final JWEObject jweObject =
+ new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+ .contentType("JWT")
+ .build(),
+ new Payload(createSignedUserInfoJWTResponseJSON(issuer, audience)));
+ jweObject.encrypt(new RSAEncrypter(keyRecipient.toPublicJWK()));
+ return EncryptedJWT.parse(jweObject.serialize());
}
/**
@@ -550,7 +594,89 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
}
@Test
- public void testAuthnFlowFromAuthorizationCallback_UsingJWTUserInfoResponse() throws Exception {
+ public void testAuthnFlowFromAuthorizationCallback_UsingSignedJWTUserInfoResponse() throws Exception {
+
+ setFlowPath(FLOW);
+ setFlowModelResources(flowResources);
+ setSubflows(subflows);
+
+ final Map<String,String> mockProperties = Map.of(
+ "idp.service.clientinfo.failFast","false",
+ "idp.oidc.rp.clientID","clientId",
+ "idp.oidc.rp.clientSecret","secret",
+ "idp.oidc.rp.providerConfigurationDocument","provider_location",
+ "idp.oidc.rp.redirectURI","https://localhost:8443/idp/profile/Authn/OIDC/RP/callback",
+ "idp.oidc.rp.scope","email",
+ "idp.entityID", "http://idp.example.com/",
+ "idp.authn.oidc.rp.proxyIssuer","https://op.example.com");
+
+ setMockProperties(mockProperties);
+
+ final MockWebServer mockOPServer = createSimpleServer();
+ // First is token exchange
+ mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+ .setHeader("content-type", "application/json")
+ .setBody(createAccessTokenResponseJSON()));
+ // Second is userInfo
+ mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+ .setHeader("content-type", "application/jwt")
+ .setBody(createSignedUserInfoJWTResponseJSON("https://op.example.com","demo_rp").serialize()));
+ mockOPServer.start(9918);
+
+
+ final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+ .createFlowExecution(getFlowDefinition());
+ final ProfileRequestContext prc = buildProfileRequestContext("authn/OIDCRelyingParty", false,false);
+ prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority("http://op.example.com");
+
+ // create a nested PRC under the authentication context
+ final ProfileRequestContext nestPrc = (ProfileRequestContext)
+ prc.getSubcontext(AuthenticationContext.class).addSubcontext(new ProfileRequestContext(), true);
+
+ // Add under nest PRC
+ final RelyingPartyContext partyContext = new RelyingPartyContext();
+ final OIDCAuthorizationConfiguration partyConfig = new OIDCAuthorizationConfiguration();
+ partyContext.setProfileConfig(partyConfig);
+ nestPrc.addSubcontext(partyContext);
+
+ // Setup outbound context
+ final MessageContext outMsgCtx = new MessageContext();
+ outMsgCtx.setMessage(createAuthenticationRequest());
+ outMsgCtx.addSubcontext(createPeerContext());
+ outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
+ outMsgCtx.addSubcontext(createClientMedataContext());
+ nestPrc.setOutboundMessageContext(outMsgCtx);
+
+ // Setup inbound context.
+ final MessageContext inMsgCtx = new MessageContext();
+ inMsgCtx.setMessage(createAuthenticationResponse());
+ nestPrc.setInboundMessageContext(inMsgCtx);
+
+ // Add prc to flow.
+ prc.getSubcontext(AuthenticationContext.class)
+ .addSubcontext(new ExternalAuthenticationContext(new ExternalAuthenticationImpl(false)));
+ flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+
+
+ updateFlowExecution(flowExecution);
+
+ //set start view and ending event to transition on.
+ externalContext.setEventId("proceed");
+ setCurrentState("AuthRequest");
+ resumeFlow(externalContext);
+
+ mockOPServer.shutdown();
+
+ //assert success conditions
+ assertFlowExecutionEnded();
+ assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+ assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
+ assertEquals(prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName(),"jdoe");
+
+ }
+
+ @Test
+ public void testAuthnFlowFromAuthorizationCallback_UsingEncryptedJWTUserInfoResponse() throws Exception {
setFlowPath(FLOW);
setFlowModelResources(flowResources);
@@ -576,7 +702,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
// Second is userInfo
mockOPServer.enqueue(new MockResponse().setResponseCode(200)
.setHeader("content-type", "application/jwt")
- .setBody(createSignedUserInfoJWTResponseJSON("https://op.example.com","demo_rp")));
+ .setBody(createSignedAndEncryptedUserInfoJWTResponseJSON("https://op.example.com","demo_rp")
+ .serialize()));
mockOPServer.start(9918);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list