[java-idp-plugin-oidc-rp] branch main updated: Add first attempt of attribute filtering to final validation stage.
Phil Smart
philip.smart at jisc.ac.uk
Wed Feb 16 13:54:57 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=de6d218794c263e336dc52f7118c8c355546c785
The following commit(s) were added to refs/heads/main by this push:
new de6d218 Add first attempt of attribute filtering to final validation stage.
de6d218 is described below
commit de6d218794c263e336dc52f7118c8c355546c785
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Feb 16 13:54:51 2022 +0000
Add first attempt of attribute filtering to final validation stage.
Add attribute passthrough to the IdP
---
.../oidc/rp/context/EndUserClaimsContext.java | 36 +++-
idp-oidc-rp-impl/pom.xml | 5 +
.../rp/impl/DefaultClaimSanitizationStrategy.java | 9 +-
...OutboundAuthorizationRequestMessageContext.java | 16 +-
...s.java => ProcessUserInfoAndIDTokenClaims.java} | 49 +++--
.../oidc/rp/impl/ValidateOIDCAuthentication.java | 223 +++++++++++++++++----
.../oidc-relying-party-authn-beans.xml | 20 +-
.../oidc-relying-party-authn-flow.xml | 42 ++--
.../rp/impl/MergeUserInfoAndIDTokenClaimsTest.java | 6 +-
.../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java | 88 ++++----
.../attribute/filter/attribute-filter-system.xml | 33 +++
.../attribute/filter/attribute-filter.xml | 34 ++++
.../resources/attribute/registry/postconfig.xml | 2 +-
.../resources/conf/test-relying-party-system.xml | 2 +-
.../src/test/resources/metadata/oidc-clients.json | 2 +-
15 files changed, 422 insertions(+), 145 deletions(-)
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/EndUserClaimsContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/EndUserClaimsContext.java
index f4c528b..098ae7c 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/EndUserClaimsContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/EndUserClaimsContext.java
@@ -22,6 +22,7 @@ import javax.annotation.Nullable;
import org.opensaml.messaging.context.BaseContext;
+import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -29,9 +30,15 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
/** A context to hold the final set of claims associated with an authenticated end-user.*/
public class EndUserClaimsContext extends BaseContext {
- /** The claims associated with the authenticated end-user.*/
+ /**
+ * The claims associated with the authenticated end-user.
+ * Often an aggregate of id_token and UserInfo claims
+ */
@Nullable private ClaimsSet endUserClaims;
+ /** The set of unprocessed id_token claims as returned from an id_token source.*/
+ @Nullable private JWTClaimsSet unprocessedIdTokenClaims;
+
/**
* Get the claims about the authenticated end-user.
*
@@ -45,9 +52,34 @@ public class EndUserClaimsContext extends BaseContext {
* Set the claims about the authenticated end-user.
*
* @param claims the claims.
+ * @return this
*/
- public void setEndUserClaims(@Nonnull final ClaimsSet claims) {
+ public EndUserClaimsContext setEndUserClaims(@Nonnull final ClaimsSet claims) {
endUserClaims = Constraint.isNotNull(claims, "Claims can not be null");
+ return this;
+ }
+
+ /**
+ * Set the id_token claims about the authenticated end-user as returned from the id_token
+ * endpoint e.g. from a successful Token Response.
+ *
+ * <p>In contrast, the endUserClaims may contain both an aggregation of claims obtained from other
+ * sources e.g. the UserInfo endpoint, and a subset of the id_token claims e.g. only 'identity' claims
+ * and not 'validation' claims.</p>
+ *
+ * @param claims the id_token claims
+ */
+ public void setUnprocessedIdTokenClaims(@Nonnull final JWTClaimsSet claims) {
+ unprocessedIdTokenClaims = Constraint.isNotNull(claims,"ID Token claims can not be null");
+ }
+
+ /**
+ * Get the unproccessed id_token claims.
+ *
+ * @return the unprocessed id_token claims.
+ */
+ @Nullable public JWTClaimsSet getUnprocessedIdTokenClaims() {
+ return unprocessedIdTokenClaims;
}
}
diff --git a/idp-oidc-rp-impl/pom.xml b/idp-oidc-rp-impl/pom.xml
index dcb64d2..69a5e33 100644
--- a/idp-oidc-rp-impl/pom.xml
+++ b/idp-oidc-rp-impl/pom.xml
@@ -128,6 +128,11 @@
<artifactId>idp-conf</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-attribute-filter-spring</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>net.shibboleth.idp</groupId>
<artifactId>idp-conf-impl</artifactId>
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
index d2f7f79..bf098b1 100644
--- 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
@@ -23,6 +23,7 @@ import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
@@ -48,11 +49,15 @@ public class DefaultClaimSanitizationStrategy implements UnaryOperator<ClaimsSet
JWTClaims.ISSUER_CLAIM.getClaimName(),
JWTClaims.ISSUED_AT_CLAIM.getClaimName(),
JWTClaims.AUDIENCE_CLAIM.getClaimName(),
- JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName());
+ JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName(),
+ "at_hash");
}
@Override
- public ClaimsSet apply(@Nonnull final ClaimsSet jwtClaims) {
+ public ClaimsSet apply(@Nullable final ClaimsSet jwtClaims) {
+ if (jwtClaims == null) {
+ return new ClaimsSet();
+ }
final ClaimsSet sanitizedClaims = new ClaimsSet();
final Map<String, Object> filteredMap = jwtClaims.toJSONObject().entrySet()
.stream()
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
index 692d844..00e5dc0 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
@@ -50,6 +50,9 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
/** The {@link OIDCPeerEntityContext} to base the outbound context on. */
@Nullable private OIDCPeerEntityContext peerEntityCtx;
+
+ /** The stashed inbound client metadata context.*/
+ @Nullable private OIDCMetadataContext inboundClientMetadata;
/** Strategy function to lookup the {@link OIDCMetadataContext} that represents this client during
* communication with the given OIDC peer. */
@@ -122,6 +125,15 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
peerEntityCtx = (OIDCPeerEntityContext) identifyingCtx;
+ inboundClientMetadata =
+ oidcClientMetadataCtxLookupStrategy.apply(profileRequestContext);
+
+ if (inboundClientMetadata == null) {
+ log.debug("{} No OIDC inbound client metadata context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+ return false;
+ }
+
return true;
}
@@ -146,9 +158,7 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
outboundPeerContext.addSubcontext(outMetadata);
}
- final OIDCMetadataContext inboundClientMetadata =
- oidcClientMetadataCtxLookupStrategy.apply(profileRequestContext);
-
+
final OIDCMetadataContext outboundClientMetadata = new OIDCMetadataContext();
outboundClientMetadata.setClientInformation(inboundClientMetadata.getClientInformation());
msgCtx.addSubcontext(outboundClientMetadata);
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/ProcessUserInfoAndIDTokenClaims.java
similarity index 85%
rename from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MergeUserInfoAndIDTokenClaims.java
rename to idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ProcessUserInfoAndIDTokenClaims.java
index 222f86e..425e0fc 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/ProcessUserInfoAndIDTokenClaims.java
@@ -40,6 +40,7 @@ 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.oidc.profile.core.OidcEventIds;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -50,10 +51,11 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
* 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 {
+//TODO ensure everything passes through this, even if no UserInfo
+public class ProcessUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAction {
/** Class logger.*/
- @Nonnull private final Logger log = LoggerFactory.getLogger(MergeUserInfoAndIDTokenClaims.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ProcessUserInfoAndIDTokenClaims.class);
/** Strategy used to look up the {@link UserInfoResponseContext}. */
@Nonnull private Function<ProfileRequestContext, UserInfoResponseContext>
@@ -61,7 +63,7 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
/** Strategy used to look up the {@link AccessTokenResponseContext} . */
@Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext>
- tokenResponseContextLookupStrategy;
+ accessTokenResponseContextLookupStrategy;
/** Strategy used to look up the {@link EndUserClaimsContext} to set the parameters for. */
@Nonnull private Function<ProfileRequestContext, EndUserClaimsContext>
@@ -83,16 +85,23 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
/** The stashed id_token claims.*/
@Nullable private JWTClaimsSet idTokenClaims;
+ /** The subject identifier taken from the id_token claims.*/
+ @Nullable private String idTokenSubject;
+
+ /** The issuer of the id_token response taken from the id_token claims.*/
+ @Nullable private String idTokenIssuer;
+
/** Constructor.*/
- public MergeUserInfoAndIDTokenClaims() {
+ public ProcessUserInfoAndIDTokenClaims() {
userInfoResponseContextLookupStrategy =
new ChildContextLookup<>(UserInfoResponseContext.class).compose(
new InboundMessageContextLookup());
- tokenResponseContextLookupStrategy =
+ accessTokenResponseContextLookupStrategy =
new ChildContextLookup<>(AccessTokenResponseContext.class).compose(
new InboundMessageContextLookup());
+ // Will create context.
endUserClaimsContextLookupStrategy =
new ChildContextLookup<>(EndUserClaimsContext.class, true).compose(
new InboundMessageContextLookup());
@@ -122,7 +131,7 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
* @param strategy the strategy to use.
*/
public void setClaimSanitizationStrategy(
- @Nonnull final UnaryOperator<ClaimsSet> strategy) {
+ @Nonnull final UnaryOperator<ClaimsSet> strategy) {
claimSanitizationStrategy = Constraint.isNotNull(strategy,
"ClaimSanatizationStrategy cannot be null");
}
@@ -165,12 +174,12 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
*
* @param strategy lookup strategy
*/
- public void setTokenResponseContextLookupStrategy(
+ public void setAccessTokenResponseContextLookupStrategy(
@Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
- tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
+ accessTokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
"TokenResponseContext lookup strategy cannot be null");
}
@@ -206,7 +215,7 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
userInfoClaims = userInfoCtx.getUserInfo().getClaimsSet();
final AccessTokenResponseContext tokenResponseCtx =
- tokenResponseContextLookupStrategy.apply(profileRequestContext);
+ accessTokenResponseContextLookupStrategy.apply(profileRequestContext);
if (tokenResponseCtx == null) {
log.debug("{} No AccessTokenResponseContext returned by lookup strategy", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
@@ -231,6 +240,19 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
}
+ idTokenSubject = idTokenClaims.getSubject();
+ if (idTokenSubject == null) {
+ log.error("{} No subject found in id_token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ID_TOKEN);
+ return false;
+ }
+ idTokenIssuer = idTokenClaims.getIssuer();
+ if (idTokenIssuer == null) {
+ log.error("{} No issuer found in id_token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ID_TOKEN);
+ return false;
+ }
+
return true;
}
@@ -247,9 +269,12 @@ public class MergeUserInfoAndIDTokenClaims extends AbstractOIDCAuthenticationAct
final ClaimsSet mergedClaims = claimMergingStrategy.apply(sanitizedUserInfoClaims, sanitizedIdTokenClaims);
- // Add to end user claims context
- endUserClaimsContextLookupStrategy.apply(profileRequestContext).setEndUserClaims(mergedClaims);
-
+ // Add to end user claims context both the merged claims, and the parsed id_token claims.
+ // The id_token claims are stashed here to avoid re-parsing downstream.
+ endUserClaimsContextLookupStrategy.apply(profileRequestContext)
+ .setEndUserClaims(mergedClaims)
+ .setUnprocessedIdTokenClaims(idTokenClaims);
+
if (log.isTraceEnabled()) {
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 aad5c6e..674957c 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
@@ -20,28 +20,43 @@ package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
+import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.security.auth.Subject;
+import org.opensaml.messaging.context.MessageContext;
import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.RecursiveTypedParentContextLookup;
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.saml.metadata.resolver.MetadataResolver;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Audience;
+import org.opensaml.saml.saml2.core.AuthenticatingAuthority;
+import org.opensaml.saml.saml2.core.AuthnContext;
+import org.opensaml.saml.saml2.core.ProxyRestriction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
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.context.AttributeContext;
import net.shibboleth.idp.attribute.filter.AttributeFilter;
+import net.shibboleth.idp.attribute.filter.AttributeFilterException;
+import net.shibboleth.idp.attribute.filter.context.AttributeFilterContext;
+import net.shibboleth.idp.attribute.filter.context.AttributeFilterContext.Direction;
import net.shibboleth.idp.attribute.transcoding.AttributeTranscoder;
import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
import net.shibboleth.idp.attribute.transcoding.TranscoderSupport;
@@ -51,10 +66,12 @@ import net.shibboleth.idp.authn.AuthenticationResult;
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.ProxyAuthenticationPrincipal;
import net.shibboleth.idp.authn.principal.UsernamePrincipal;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.saml.profile.context.navigate.SAMLMetadataContextLookupFunction;
import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
import net.shibboleth.utilities.java.support.annotation.constraint.Live;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -80,8 +97,6 @@ import net.shibboleth.utilities.java.support.service.ServiceableComponent;
* .getJWTClaimsSet().getSubject()!= null, then an {@link net.shibboleth.idp.authn.AuthenticationResult}
* is saved to the {@link AuthenticationContext}.
*/
-//TODO if we pull claims from a UserInfo endpoint, we need to augment with claims in the id_token and this
-// only looks at the id_token.
public class ValidateOIDCAuthentication extends AbstractValidationAction {
/** Default prefix for metrics. */
@@ -96,19 +111,25 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
/** Service used to get the engine used to filter attributes. */
@Nullable private ReloadableService<AttributeFilter> attributeFilterService;
+ /** Optional supplemental metadata source for filtering. */
+ @Nullable private MetadataResolver metadataResolver;
+
/** 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;
-
- /** End-user claims Set to extract and transcode claims from. */
- @Nullable private ClaimsSet claimsSet;
+ /** The context with claims pertaining to the end-user of this authentication.*/
+ @Nullable private EndUserClaimsContext endUserContext;
+
/** Strategy used to look up the {@link EndUserClaimsContext} to set the parameters for. */
@Nonnull private Function<ProfileRequestContext, EndUserClaimsContext>
endUserClaimsContextLookupStrategy;
+ /** Context for externally supplied inbound attributes. */
+ @Nullable private AttributeContext attributeContext;
+
/** Constructor.*/
public ValidateOIDCAuthentication() {
setMetricName(DEFAULT_METRIC_NAME);
@@ -142,6 +163,17 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
transcoderRegistry = Constraint.isNotNull(registry, "AttributeTranscoderRegistry cannot be null");
}
+ /**
+ * Set a metadata source to use during filtering.
+ *
+ * @param resolver metadata resolver
+ */
+ public void setMetadataResolver(@Nullable final MetadataResolver resolver) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ metadataResolver = resolver;
+ }
+
/**
* Set the strategy used to return the {@link RelyingPartyContext} for configuration options.
*
@@ -154,28 +186,14 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
relyingPartyContextLookupStrategy =
Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
}
-
- /**
- * In MFA use case prior authentication may have created a usernameprincipal already with value not matching to MFA.
- *
- * @param avoid true if additional principals should be avoided.
- */
- public void setAvoidMultiplePrincipal(final boolean avoid) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- avoidMultiplePrincipal = avoid;
- }
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
-
-// if (transcoderRegistry == null) {
-// throw new ComponentInitializationException("AttributeTranscoderRegistry cannot be null");
-// }
+
}
- /** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@Nonnull final AuthenticationContext authenticationContext) {
@@ -183,6 +201,25 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
return false;
}
+
+ final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
+ if (inboundMessageCtx == null) {
+ log.error("{} No inbound message context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ if (inboundMessageCtx.getMessage() == null) {
+ log.error("{} No inbound message", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ if (!(inboundMessageCtx.getMessage() instanceof AuthenticationSuccessResponse)) {
+ log.error("{} No inbound authentication success response", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
final RelyingPartyContext rpContext = relyingPartyContextLookupStrategy.apply(profileRequestContext);
if (rpContext == null) {
@@ -200,18 +237,22 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
}
profileConfiguration = (OIDCAuthorizationConfiguration) rpContext.getProfileConfig();
- final EndUserClaimsContext claimsContext = endUserClaimsContextLookupStrategy.apply(profileRequestContext);
- if (claimsContext == null) {
+ endUserContext = endUserClaimsContextLookupStrategy.apply(profileRequestContext);
+ if (endUserContext == null) {
log.error("{} Unable to locate end-user claims context", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return false;
}
- if (claimsContext.getEndUserClaims() == null) {
- log.error("{} Unable to locate end-user claims", getLogPrefix());
+ if (endUserContext.getEndUserClaims() == null) {
+ log.error("{} End-user claims are null", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+ if (endUserContext.getUnprocessedIdTokenClaims() == null) {
+ log.error("{} Id_token not found in response", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return false;
}
- claimsSet = claimsContext.getEndUserClaims();
return true;
}
@@ -228,27 +269,62 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
if (transcoderRegistry != null) {
processAttributes(profileRequestContext);
}
- //TODO use an attributeExtractionStrategy
+ //TODO use an attributeExtractionStrategy if registry not supplied, or in addition?
buildAuthenticationResult(profileRequestContext, authenticationContext);
- return;
+
+// if (authenticationContext.getAuthenticationResult() != null
+// && profileConfiguration.isProxiedAuthnInstant(profileRequestContext)) {
+// log.debug("{} Resetting authentication time to proxied value: {}", getLogPrefix(),
+// samlAuthnContext.getAuthnStatement().getAuthnInstant());
+// if (samlAuthnContext.getAuthnStatement().getAuthnInstant() != null) {
+// authenticationContext.getAuthenticationResult().setAuthenticationInstant(
+// samlAuthnContext.getAuthnStatement().getAuthnInstant());
+// }
+// }
+
}
@Override
- protected Subject populateSubject(@Nonnull final Subject subject) {
+ protected Subject populateSubject(@Nonnull final Subject subject) {
- if (avoidMultiplePrincipal && subject.getPrincipals().size() > 0) {
- log.debug("{} Subject already contains principal, not populated", getLogPrefix());
-
- } else {
- subject.getPrincipals().add(new UsernamePrincipal(claimsSet.getStringClaim("sub")));
- //subject.getPrincipals().addAll(buildIdPAttributePrincipalsFromStandardClaims());
+ //Add ACR from OIDC request/response
- }
+ //What type of subject? switch on subject_types_supported and subject_type.
+ //New principals?
+ subject.getPrincipals().add(new UsernamePrincipal(endUserContext.getUnprocessedIdTokenClaims().getSubject()));
+ subject.getPrincipals().add(buildProxyPrincipal());
+
+ if (attributeContext != null && !attributeContext.getIdPAttributes().isEmpty()) {
+ log.debug("{} Adding filtered inbound attributes to Subject", getLogPrefix());
+ subject.getPrincipals().addAll(
+ attributeContext.getIdPAttributes().values()
+ .stream()
+ .map(IdPAttributePrincipal::new)
+ .collect(Collectors.toUnmodifiableList()));
+ }
+
return subject;
}
+ /**
+ * TODO FINISH Construct a populated {@link ProxyAuthenticationPrincipal} based on the inbound id_token
+ * response.
+ *
+ * @return a constructed {@link ProxyAuthenticationPrincipal} to include in the {@link Subject}
+ */
+ @Nonnull private ProxyAuthenticationPrincipal buildProxyPrincipal() {
+
+ final ProxyAuthenticationPrincipal proxied = new ProxyAuthenticationPrincipal();
+
+ proxied.getAuthorities().add(endUserContext.getUnprocessedIdTokenClaims().getIssuer());
+
+ // There is no proxy audience I know of in the OIDC or OAuth spec like there is in saml?
+
+ return proxied;
+ }
+
/**
* Process the inbound OIDC claims.
*
@@ -268,9 +344,9 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
return;
}
- for (final Map.Entry<String, Object> claim : claimsSet.toJSONObject().entrySet()) {
+ for (final Map.Entry<String, Object> claim :
+ endUserContext.getEndUserClaims().toJSONObject().entrySet()) {
try {
- //Only allow the standard claims? so no exp. etc
final JSONObject jsonClaim = new JSONObject();
jsonClaim.put(claim.getKey(), claim.getValue());
decodeAttribute(component.getComponent(), profileRequestContext, jsonClaim, mapped);
@@ -287,15 +363,76 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
log.debug("{} Incoming OIDC Attributes mapped to attribute IDs: {}", getLogPrefix(), mapped.keySet());
if (!mapped.isEmpty()) {
-// attributeContext = profileRequestContext
-// .getSubcontext(RelyingPartyContext.class)
-// .getSubcontext(AttributeContext.class, true);
-// attributeContext.setUnfilteredIdPAttributes(mapped.values());
-// attributeContext.setIdPAttributes(null);
-// filterAttributes(profileRequestContext);
+ attributeContext = profileRequestContext
+ .getSubcontext(RelyingPartyContext.class)
+ .getSubcontext(AttributeContext.class, true);
+ attributeContext.setUnfilteredIdPAttributes(mapped.values());
+ attributeContext.setIdPAttributes(null);
+ filterAttributes(profileRequestContext);
}
}
+ /**
+ * Check for inbound attributes and apply filtering.
+ *
+ * @param profileRequestContext current profile request context
+ */
+ private void filterAttributes(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (attributeFilterService == null) {
+ log.warn("{} No AttributeFilter service provided", getLogPrefix());
+ return;
+ }
+
+ final AttributeFilterContext filterContext =
+ profileRequestContext.getSubcontext(AttributeFilterContext.class, true);
+
+ populateFilterContext(profileRequestContext, filterContext);
+
+ ServiceableComponent<AttributeFilter> component = null;
+
+ try {
+ component = attributeFilterService.getServiceableComponent();
+ if (null == component) {
+ log.error("{} Error while filtering inbound attributes: Invalid Attribute Filter configuration",
+ getLogPrefix());
+ } else {
+ final AttributeFilter filter = component.getComponent();
+ filter.filterAttributes(filterContext);
+ filterContext.getParent().removeSubcontext(filterContext);
+ attributeContext.setIdPAttributes(filterContext.getFilteredIdPAttributes().values());
+ }
+ } catch (final AttributeFilterException e) {
+ log.error("{} Error while filtering inbound attributes", getLogPrefix(), e);
+ } finally {
+ if (null != component) {
+ component.unpinComponent();
+ }
+ }
+ }
+
+ /**
+ * Fill in the filter context data.
+ *
+ * @param profileRequestContext current profile request context
+ * @param filterContext context to populate
+ */
+ private void populateFilterContext(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AttributeFilterContext filterContext) {
+
+ filterContext.setDirection(Direction.INBOUND)
+ .setPrefilteredIdPAttributes(attributeContext.getUnfilteredIdPAttributes().values())
+ .setMetadataResolver(metadataResolver)
+ .setRequesterMetadataContextLookupStrategy(null)
+ // FIXME OIDC? depends if this is now for upstream?
+ .setIssuerMetadataContextLookupStrategy(
+ new SAMLMetadataContextLookupFunction().compose(
+ new RecursiveTypedParentContextLookup<>(ProfileRequestContext.class)))
+ // OIDC ^
+ .setProxiedRequesterContextLookupStrategy(null)
+ .setAttributeIssuerID(getResponderLookupStrategy().apply(profileRequestContext))
+ .setAttributeRecipientID(getRequesterLookupStrategy().apply(profileRequestContext));
+ }
+
/**
* Access the registry of transcoding rules to transform (decode) the input claims to IdP Attributes.
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 ff70537..0dc6a1f 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
@@ -398,12 +398,6 @@
<!-- missing ACR? and auth_time, access_token at_hash. -->
</util:list>
- <bean id="ValidateOIDCAuthentication"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOIDCAuthentication" scope="prototype"
- p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
- p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
- p:transcoderRegistry-ref="shibboleth.AttributeRegistryService"/>
-
<!-- UserInfo endpoint beans -->
@@ -430,8 +424,8 @@
p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"/>
- <bean id="MergeUserInfoAndIDTokenClaims"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.MergeUserInfoAndIDTokenClaims" scope="prototype"
+ <bean id="ProcessUserInfoAndIDTokenClaims"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ProcessUserInfoAndIDTokenClaims" scope="prototype"
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"/>
@@ -462,6 +456,16 @@
+ <!-- Final validation and proxy authentication result -->
+
+ <bean id="ValidateOIDCAuthentication"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOIDCAuthentication" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
+ p:responderLookupStrategy-ref="shibboleth.RelyingPartyIdLookup.Simple"
+ p:requesterLookupStrategy-ref="shibboleth.ResponderIdLookup.Simple"
+ p:attributeFilter-ref="shibboleth.AttributeFilterService"
+ p:transcoderRegistry-ref="shibboleth.AttributeRegistryService"/>
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 90af8f1..99b96f4 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
@@ -95,23 +95,6 @@
<transition on="proceed" to="ValidateToken" />
</action-state>
- <action-state id="HybridFlow">
-
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="ValidateToken" />
- </action-state>
-
- <action-state id="ImplicitFlow">
-
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="ValidateToken" />
- </action-state>
-
- <action-state id="UnsupportedFlow">
-
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="END" />
- </action-state>
<!-- TODO claim validation will differ per grant_type -->
<action-state id="ValidateToken">
@@ -125,7 +108,8 @@
<!-- Should we request information from the UserInfo endpoint based on profile config -->
<decision-state id="CheckUserInfoRequired">
<if test="true"
- then="UserInfoRequest" />
+ then="UserInfoRequest"
+ else="FinalizeResponse" />
<!-- if else here, we need to set id_token claims into EndUserClaimsContext -->
</decision-state>
@@ -168,16 +152,32 @@
<action-state id="ValidateUserInfoClaimsSet">
<evaluate expression="ValidateUserInfoClaims" />
- <evaluate expression="MergeUserInfoAndIDTokenClaims" />
<evaluate expression="'proceed'" />
- <transition on="proceed" to="SetPrincipal" />
+ <transition on="proceed" to="FinalizeResponse" />
</action-state>
- <action-state id="SetPrincipal">
+ <action-state id="FinalizeResponse">
+ <evaluate expression="ProcessUserInfoAndIDTokenClaims" /> <!-- check this works if no userInfo -->
<evaluate expression="ValidateOIDCAuthentication" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="proceed" />
</action-state>
+
+ <!-- Placeholders for flows which are not supported, and would not work without some front-end impl. -->
+ <action-state id="HybridFlow">
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="UnsupportedFlow" />
+ </action-state>
+
+ <action-state id="ImplicitFlow">
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="UnsupportedFlow" />
+ </action-state>
+
+ <action-state id="UnsupportedFlow">
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="END" />
+ </action-state>
<bean-import resource="oidc-relying-party-authn-beans.xml" />
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
index f4ea4db..b974b9e 100644
--- 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
@@ -47,17 +47,17 @@ import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileR
import net.shibboleth.oidc.security.jwt.claims.impl.JWTClaims;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-/** Tests for {@link MergeUserInfoAndIDTokenClaims}.*/
+/** Tests for {@link ProcessUserInfoAndIDTokenClaims}.*/
public class MergeUserInfoAndIDTokenClaimsTest extends AbstractOIDCTest {
/** Action to test.*/
- private MergeUserInfoAndIDTokenClaims action;
+ private ProcessUserInfoAndIDTokenClaims action;
@BeforeMethod
public void setup() throws Exception {
super.setup();
- action = new MergeUserInfoAndIDTokenClaims();
+ action = new ProcessUserInfoAndIDTokenClaims();
final AccessTokenResponseContext trc = new AccessTokenResponseContext();
final PlainJWT jwt = new PlainJWT(new JWTClaimsSet.Builder()
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 3137d71..df03349 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
@@ -63,7 +63,6 @@ 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;
@@ -90,6 +89,7 @@ import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContex
import net.shibboleth.idp.plugin.authn.test.flow.AbstractAuthnXmlFlowExecutionTests;
import net.shibboleth.idp.plugin.authn.test.flow.mock.MockFlowBuilder;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.relyingparty.RelyingPartyConfiguration;
import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
@@ -104,13 +104,15 @@ import okhttp3.tls.HeldCertificate;
/** Test the OIDC relying party flow.*/
public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
+
+ private static final String OP_ISSUER_ID = "https://localhost:9918";
/**
* Example of good provider metadata. Endpoints are localhost to support the
* mock server that is started.
*/
- private final String GOOD_PROVIDER_CONFIGURATION_INFO = "{\n"
- + "\"issuer\": \"https://op.example.com\",\n"
+ private final static String GOOD_PROVIDER_CONFIGURATION_INFO = "{\n"
+ + "\"issuer\": \"https://localhost:9918\",\n"
+ "\"authorization_endpoint\": \"https://localhost:9918/o/oauth2/v2/auth\",\n"
+ "\"device_authorization_endpoint\": \"https://localhost:9918/device/code\",\n"
+ "\"token_endpoint\": \"https://localhost:9918/token\",\n"
@@ -170,7 +172,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
@Nonnull private final String CLIENT_METADATA = "[\n"
+ " {\n"
- + " \"issuer\": \"https://op.example.com\",\n"
+ + " \"issuer\": \""+OP_ISSUER_ID+"\",\n"
+ " \"scope\": \"openid info profile email address phone\",\n"
+ " \"redirect_uris\": [\n"
+ " \"https://192.168.0.150/static\"\n"
@@ -278,10 +280,14 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
loadBeanDefinitionsFromXmlFile(builderContext,
new ClassPathResource("conf/test-relyingparty-resolver-service.xml"));
- loadBeanDefinitionsFromXmlFile(builderContext, new ClassPathResource("conf/additional-system-beans.xml"));
+ loadBeanDefinitionsFromXmlFile(builderContext,
+ new ClassPathResource("conf/additional-system-beans.xml"));
loadBeanDefinitionsFromXmlFile(builderContext,
new ClassPathResource("attribute/registry/postconfig.xml"));
+
+ loadBeanDefinitionsFromXmlFile(builderContext,
+ new ClassPathResource("attribute/filter/attribute-filter-system.xml"));
}
/**
@@ -299,7 +305,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
.type(JOSEObjectType.JWT)
.build();
final var payload = new JWTClaimsSet.Builder()
- .issuer("https://op.example.com")
+ .issuer(OP_ISSUER_ID)
.audience(List.of("demo_rp","demo_rp2"))
.subject("jdoe")
.claim("nonce", "abadnonce")
@@ -420,7 +426,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
* @return the authentication request.
*/
private OIDCAuthenticationRequest createAuthenticationRequest() {
- final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://op.example.com"));
+ final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID(OP_ISSUER_ID));
request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
request.setNonce(new Nonce("abadnonce"));
return request;
@@ -475,23 +481,26 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
}
@Test
- public void testFlowToAuthorizationRedirect() {
+ public void testFlowToAuthorizationRedirect() 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");
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
setMockProperties(mockProperties);
+ final MockWebServer mockOPServer = createSimpleServer();
+ // First is metadata exchange
+ mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+ .setHeader("content-type", "application/json")
+ .setBody(GOOD_PROVIDER_CONFIGURATION_INFO));
+
+ mockOPServer.start(9918);
+
final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<Object>();
inputMap.put("calledAsSubflow", true);
@@ -517,15 +526,9 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
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",
+ final Map<String,String> mockProperties = Map.of(
"idp.entityID", "http://idp.example.com/",
- "idp.authn.oidc.rp.proxyIssuer","https://op.example.com");
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
setMockProperties(mockProperties);
@@ -544,7 +547,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
.createFlowExecution(getFlowDefinition());
final ProfileRequestContext prc = buildProfileRequestContext("authn/OIDCRelyingParty", false,false);
- prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority("http://op.example.com");
+ prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority(OP_ISSUER_ID);
// create a nested PRC under the authentication context
final ProfileRequestContext nestPrc = (ProfileRequestContext)
@@ -554,6 +557,9 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
final RelyingPartyContext partyContext = new RelyingPartyContext();
final OIDCAuthorizationConfiguration partyConfig = new OIDCAuthorizationConfiguration();
partyContext.setProfileConfig(partyConfig);
+ final RelyingPartyConfiguration rPartyConfig = new RelyingPartyConfiguration();
+ rPartyConfig.setResponderId("http://idp.example.com/");
+ partyContext.setConfiguration(rPartyConfig);
nestPrc.addSubcontext(partyContext);
// Setup outbound context
@@ -602,13 +608,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
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");
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
setMockProperties(mockProperties);
@@ -620,14 +621,14 @@ 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").serialize()));
+ .setBody(createSignedUserInfoJWTResponseJSON(OP_ISSUER_ID,"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");
+ prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority(OP_ISSUER_ID);
// create a nested PRC under the authentication context
final ProfileRequestContext nestPrc = (ProfileRequestContext)
@@ -671,12 +672,13 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
assertFlowExecutionEnded();
assertNotNull(prc.getSubcontext(AuthenticationContext.class));
assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
- assertEquals(prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName(),"jdoe");
+ assertEquals("jdoe", prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName());
}
@Test
- public void testAuthnFlowFromAuthorizationCallback_UsingEncryptedJWTUserInfoResponse() throws Exception {
+ public void testAuthnFlowFromAuthorizationCallback_UsingEncryptedJWTUserInfoResponse()
+ throws Exception {
setFlowPath(FLOW);
setFlowModelResources(flowResources);
@@ -684,13 +686,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
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");
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
setMockProperties(mockProperties);
@@ -702,7 +699,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
// Second is userInfo
mockOPServer.enqueue(new MockResponse().setResponseCode(200)
.setHeader("content-type", "application/jwt")
- .setBody(createSignedAndEncryptedUserInfoJWTResponseJSON("https://op.example.com","demo_rp")
+ .setBody(createSignedAndEncryptedUserInfoJWTResponseJSON(OP_ISSUER_ID,"demo_rp")
.serialize()));
mockOPServer.start(9918);
@@ -710,7 +707,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
.createFlowExecution(getFlowDefinition());
final ProfileRequestContext prc = buildProfileRequestContext("authn/OIDCRelyingParty", false,false);
- prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority("http://op.example.com");
+ prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority(OP_ISSUER_ID);
// create a nested PRC under the authentication context
final ProfileRequestContext nestPrc = (ProfileRequestContext)
@@ -776,13 +773,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
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");
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
setMockProperties(mockProperties);
@@ -799,7 +791,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
prc.getSubcontext(AuthenticationContext.class).addSubcontext(new ProfileRequestContext(), true);
final MessageContext outMsgCtx = new MessageContext();
- final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://op.example.com"));
+ final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID(OP_ISSUER_ID));
request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
outMsgCtx.setMessage(request);
nestPrc.setOutboundMessageContext(outMsgCtx);
diff --git a/idp-oidc-rp-impl/src/test/resources/attribute/filter/attribute-filter-system.xml b/idp-oidc-rp-impl/src/test/resources/attribute/filter/attribute-filter-system.xml
new file mode 100644
index 0000000..ae21ec2
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/attribute/filter/attribute-filter-system.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize"
+ default-destroy-method="destroy">
+
+ <bean id="shibboleth.AttributeFilterService" class="net.shibboleth.ext.spring.service.ReloadableSpringService"
+ depends-on="shibboleth.VelocityEngine"
+ p:failFast="%{idp.service.attribute.filter.failFast:%{idp.service.failFast:false}}"
+ p:reloadCheckDelay="%{idp.service.attribute.filter.checkInterval:PT0S}"
+ p:beanPostProcessors-ref="shibboleth.IdentifiableBeanPostProcessor"
+ p:beanFactoryPostProcessors-ref="shibboleth.PropertySourcesPlaceholderConfigurer">
+ <constructor-arg name="claz" value="net.shibboleth.idp.attribute.filter.AttributeFilter" />
+ <constructor-arg name="strategy">
+ <bean class="net.shibboleth.idp.attribute.filter.spring.impl.AttributeFilterServiceStrategy"
+ id="ShibbolethAttributeFilter"/>
+ </constructor-arg>
+ <property name="serviceConfigurations">
+ <util:list>
+ <value>attribute/filter/attribute-filter.xml</value>
+ </util:list>
+ </property>
+ </bean>
+
+</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/resources/attribute/filter/attribute-filter.xml b/idp-oidc-rp-impl/src/test/resources/attribute/filter/attribute-filter.xml
new file mode 100644
index 0000000..7f77ede
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/attribute/filter/attribute-filter.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ This file is an EXAMPLE policy file. While the policy presented in this
+ example file is illustrative of some simple cases, it relies on the names of
+ non-existent example services and the example attributes demonstrated in the
+ default attribute-resolver.xml file.
+
+ This example does contain some usable "general purpose" policies that may be
+ useful in conjunction with specific deployment choices, but those policies may
+ not be applicable to your specific needs or constraints.
+-->
+<AttributeFilterPolicyGroup id="ShibbolethFilterPolicy"
+ xmlns="urn:mace:shibboleth:2.0:afp"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="urn:mace:shibboleth:2.0:afp http://shibboleth.net/schema/idp/shibboleth-afp.xsd">
+
+
+ <!-- Release displayName to everybody. -->
+ <AttributeFilterPolicy id="alwaysRelease">
+ <PolicyRequirementRule xsi:type="ANY" />
+
+ <AttributeRule attributeID="displayName" permitAny="true" />
+ </AttributeFilterPolicy>
+
+ <!-- Release an additional attribute if the issuer is the mock downstream OP. -->
+ <AttributeFilterPolicy id="example1">
+ <PolicyRequirementRule xsi:type="Issuer" value="https://localhost:9918" />
+
+ <AttributeRule attributeID="uid" permitAny="true" />
+ </AttributeFilterPolicy>
+
+
+
+</AttributeFilterPolicyGroup>
diff --git a/idp-oidc-rp-impl/src/test/resources/attribute/registry/postconfig.xml b/idp-oidc-rp-impl/src/test/resources/attribute/registry/postconfig.xml
index 29752a5..12132eb 100644
--- a/idp-oidc-rp-impl/src/test/resources/attribute/registry/postconfig.xml
+++ b/idp-oidc-rp-impl/src/test/resources/attribute/registry/postconfig.xml
@@ -26,7 +26,7 @@
</constructor-arg>
<property name="serviceConfigurations">
<util:list>
- <value>attribute/registry/attribute-registry.xml</value> <!-- load itself -->
+ <value>attribute/registry/attribute-registry.xml</value>
</util:list>
</property>
</bean>
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/test-relying-party-system.xml b/idp-oidc-rp-impl/src/test/resources/conf/test-relying-party-system.xml
index b38e6e1..d1e28d8 100644
--- a/idp-oidc-rp-impl/src/test/resources/conf/test-relying-party-system.xml
+++ b/idp-oidc-rp-impl/src/test/resources/conf/test-relying-party-system.xml
@@ -43,7 +43,7 @@
<!-- Container for any overrides you want to add. -->
<util:list id="shibboleth.RelyingPartyOverrides">
-
+
</util:list>
diff --git a/idp-oidc-rp-impl/src/test/resources/metadata/oidc-clients.json b/idp-oidc-rp-impl/src/test/resources/metadata/oidc-clients.json
index 0a8e6fa..496b2cd 100644
--- a/idp-oidc-rp-impl/src/test/resources/metadata/oidc-clients.json
+++ b/idp-oidc-rp-impl/src/test/resources/metadata/oidc-clients.json
@@ -1,6 +1,6 @@
[
{
- "issuer": "https://op.example.com",
+ "issuer": "https://localhost:9918",
"scope": "openid info profile email address phone",
"redirect_uris": [
"https://192.168.0.150/static"
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list