[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