[java-idp-plugin-oidc-rp] branch main updated: Add ACR and AMR response mappings. Add proxied authn time.

Phil Smart philip.smart at jisc.ac.uk
Thu Sep 15 16:03:08 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=b00649a193880235f0903e9fb7bfbd365aeb48d7

The following commit(s) were added to refs/heads/main by this push:
     new b00649a  Add ACR and AMR response mappings. Add proxied authn time.
b00649a is described below

commit b00649a193880235f0903e9fb7bfbd365aeb48d7
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Sep 15 17:03:01 2022 +0100

    Add ACR and AMR response mappings. Add proxied authn time.
---
 ...ticationContextClassRequestLookupFunction.java} |   6 +-
 ...ticationContextClassResponseLookupFunction.java | 115 +++++++++++++++++++
 ...thenticationContextClassLookupFunctionTest.java |   4 +-
 ...tionContextClassResponseLookupFunctionTest.java | 124 +++++++++++++++++++++
 .../oidc/rp/impl/ValidateOIDCAuthentication.java   |  86 +++++++++++---
 .../idp/service/relying-party/postconfig.xml       |  20 +++-
 .../authn/oidc/rp/conf/authn/oidc-rp-config.xml    |  14 ---
 .../authn/oidc/rp/impl/AbstractOIDCTest.java       |   5 +-
 .../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java  |  89 ++++++++++++++-
 .../plugin/authn/oidc/rp/impl/TestTokenHelper.java |   2 +
 .../rp/impl/ValidateOIDCAuthenticationTest.java    |  82 ++++++++++++--
 11 files changed, 498 insertions(+), 49 deletions(-)

diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction.java
similarity index 95%
rename from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction.java
rename to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction.java
index 0c53f8c..b7af435 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction.java
@@ -44,14 +44,14 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElemen
  * 'passed through' or mapped by the given principal mappings. All input values are either SAML ACRs or AMRs,
  * and all output values are OIDC ACRs.</p>
  */
-public class ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction 
+public class ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction 
     implements Function<ProfileRequestContext,Collection<AuthenticationContextClassReferencePrincipal>> {
     
     /** Mappings to transform proxied Principals. */
     @Nonnull @NonnullElements private Map<Principal,Collection<Principal>> principalMappings;
     
     /** Constructor. */
-    public ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction() {
+    public ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction() {
         principalMappings = Collections.emptyMap();
     }
     
@@ -97,7 +97,7 @@ public class ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction
                         }
                     } else {
                         // If no mapping exists, just convert to correct output type - which might not make sense
-                        // to the downstream OP.
+                        // to the upstream OP.
                         mappedAndPassedThroughPrincipals.add(
                                 new AuthenticationContextClassReferencePrincipal(requestedPrincipal.getName()));
                     }
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction.java
new file mode 100644
index 0000000..7b6860d
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Implements a set of logic for determining the principals to use from OIDC 'amr' or 'acr' claims.
+ * 
+ * <p>This operates for the OIDC to SAML proxy use case, in effect the reverse of the 
+ * {@link ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction} function. 
+ * All input values are either OIDC ACRs or AMRs, and all output values Java {@link Principal}s.
+ * The values are mapped by the given principal mappings. If a mapping does not exist, the AMR or ACR is ignored.
+ * </p>
+ */
+ at ThreadSafe
+public class ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction 
+    implements Function<Collection<String>,Collection<Principal>> {
+    
+    /** Mappings to transform proxied Principals. */
+    @Nonnull @NonnullElements private final Map<String,Collection<Principal>> principalMappings;
+    
+    /**
+     * 
+     * Constructor.
+     *
+     * @param mappings the AMR/ACR value to Principal mappings
+     */
+    public ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction(
+            @Nullable @NonnullElements @ParameterName(name="mappings") 
+            final Map<String,Collection<Principal>> mappings) {
+        
+        if (mappings == null || mappings.isEmpty()) {
+            principalMappings = Collections.emptyMap();
+        } else {        
+            principalMappings = new HashMap<>(mappings.size());
+            mappings.forEach((k, v) -> principalMappings.put(k, List.copyOf(v)));
+        }
+    }
+    
+    @Override
+    public Collection<Principal> apply(final Collection<String> amrOrAcrs) {
+        
+        if (amrOrAcrs != null) {                
+                final List<Principal> principals = new ArrayList<>();                
+                for (final String amrOrAcr : amrOrAcrs) {                    
+                    if (principalMappings.containsKey(amrOrAcr)) {
+                        final Collection<Principal> mappedPrincipals = principalMappings.get(amrOrAcr);
+                        if (!mappedPrincipals.isEmpty()) {
+                            principals.addAll(mappedPrincipals);
+                        }
+                    }                 
+                }
+                return principals;            
+        }        
+        return Collections.emptyList();
+    }   
+    
+    /** A simple lookup function that returns a singleton function.*/
+    public static class LookupFunctionWrapper 
+                implements Function<ProfileRequestContext, Function<Collection<String>,Collection<Principal>>> {
+
+        /** A function used to map OIDC ACR/AMRs to Principals. A single instance is supplied.*/
+        private final Function<Collection<String>,Collection<Principal>> function; 
+        
+        /**
+         * 
+         * Constructor.
+         *
+         * @param wrappedFunction the function to return when requested.          
+         */
+        public LookupFunctionWrapper(@ParameterName(name="wrappedFunction") 
+                final Function<Collection<String>,Collection<Principal>> wrappedFunction) {
+            function = Constraint.isNotNull(wrappedFunction, "Lookup function can not be null");
+        }
+        
+        @Override
+        public Function<Collection<String>, Collection<Principal>> apply(final ProfileRequestContext prc) {
+            return function;
+        }
+        
+    }
+}
+
diff --git a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest.java b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest.java
index e4180a8..dced1f6 100644
--- a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest.java
+++ b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest.java
@@ -20,7 +20,7 @@ import net.shibboleth.oidc.authn.principal.AuthenticationContextClassReferencePr
 /** Tests for ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction.*/
 public class ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest {
     
-    private ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction function;
+    private ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction function;
     
     private ProfileRequestContext prc;
     
@@ -32,7 +32,7 @@ public class ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest {
     
     @BeforeMethod
     public void setup() {
-        function = new ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction();
+        function = new ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction();
         prc = new ProfileRequestContext();
         ac = prc.getSubcontext(AuthenticationContext.class, true);
         nestedPrc = ac.getSubcontext(ProfileRequestContext.class, true);
diff --git a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunctionTest.java b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunctionTest.java
new file mode 100644
index 0000000..8e3cac7
--- /dev/null
+++ b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunctionTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
+import net.shibboleth.idp.saml.authn.principal.AuthenticationMethodPrincipal;
+
+/** Tests for ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction.*/
+public class ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunctionTest {
+    
+    private ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction function;
+    
+    private ProfileRequestContext prc;
+    
+    private ProfileRequestContext nestedPrc;
+    
+    private AuthenticationContext ac;
+    
+    private RequestedPrincipalContext rpc;
+    
+    @BeforeMethod
+    public void setup() {        
+        prc = new ProfileRequestContext();
+        ac = prc.getSubcontext(AuthenticationContext.class, true);
+        nestedPrc = ac.getSubcontext(ProfileRequestContext.class, true);
+        rpc = ac.getSubcontext(RequestedPrincipalContext.class, true);
+    }
+    
+    @Test
+    public void testSingleMappingSuccess() {
+        
+        final Map<String,Collection<Principal>> mappings = new HashMap<>();
+        mappings.put(
+                "pwd", 
+                List.of(new AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")));
+
+        function = new ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction(mappings);
+        
+        final Collection<Principal> mapped = function.apply(List.of("pwd"));
+        assertTrue(mapped.contains(
+                        new AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")));
+        
+    }
+    
+    @Test
+    public void testSingleMappingSuccess_NotMappedIgnored() {
+        
+        
+        final Map<String,Collection<Principal>> mappings = new HashMap<>();
+        mappings.put(
+                "pwd", 
+                List.of(new AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")));
+        function = new ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction(mappings);;
+        
+        // OTP should not be in the result
+        final Collection<Principal> mapped = function.apply(List.of("pwd","otp"));
+        assertEquals(mapped.size(), 1);
+        assertTrue(mapped.contains(
+                        new AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")));
+       
+    }
+    
+    @Test
+    public void testSingleMappingSuccess_EmptyInput() {
+        
+        // OTP should not be in the result
+        final Collection<Principal> mapped = function.apply(Collections.emptyList());
+        assertEquals(mapped.size(), 0);
+    }
+    
+    @Test
+    public void testSingleMappingSuccess_NullMappings() {
+        
+        function = new ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction(null);
+        
+        // OTP should not be in the result
+        final Collection<Principal> mapped = function.apply(Collections.emptyList());
+        assertEquals(mapped.size(), 0);
+    }
+    
+    @Test
+    public void testSingleMappingSuccess_EmptyMapping() {
+        
+        final Map<String,Collection<Principal>> mappings = new HashMap<>();
+        mappings.put("pwd", Collections.emptyList());
+        function = new ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction(mappings);
+        
+        // OTP should not be in the result
+        final Collection<Principal> mapped = function.apply(Collections.emptyList());
+        assertEquals(mapped.size(), 0);
+    }
+
+
+}
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 18b2243..ea84f2a 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
@@ -17,7 +17,11 @@
 
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
+import java.security.Principal;
+import java.text.ParseException;
 import java.util.Collection;
+import java.util.Date;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -60,7 +64,6 @@ import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
 import net.shibboleth.idp.authn.principal.ProxyAuthenticationPrincipal;
-import net.shibboleth.idp.authn.principal.UsernamePrincipal;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.principal.OIDCSubjectIdentifierPrincipal;
 import net.shibboleth.idp.profile.IdPEventIds;
@@ -124,6 +127,12 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
     /** Context for externally supplied inbound attributes. */
     @Nullable private AttributeContext attributeContext;
     
+    /** Incoming context translation function for converting OIDC ACRs into Principals. */
+    @Nullable private Function<Collection<String>, Collection<Principal>> acrTranslator;
+    
+    /** Incoming context translation function for converting OIDC ACRs into Principals. */
+    @Nullable private Function<Collection<String>, Collection<Principal>> amrTranslator;
+    
     /** Constructor.*/
     public ValidateOIDCAuthentication() {
         setMetricName(DEFAULT_METRIC_NAME);
@@ -213,7 +222,7 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
             return false;
         }
-        
+           
 
         final RelyingPartyContext rpContext = relyingPartyContextLookupStrategy.apply(profileRequestContext);
         if (rpContext == null) {
@@ -265,25 +274,68 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
         }
         //TODO use an attributeExtractionStrategy if registry not supplied, or in addition?
         
+        acrTranslator = 
+                profileConfiguration.getAuthenticationContextClassReferenceTranslationStrategy(profileRequestContext);
+        amrTranslator = 
+                profileConfiguration.getAuthenticationMethodsReferencesTranslationStrategy(profileRequestContext);
+        
         buildAuthenticationResult(profileRequestContext, authenticationContext);
         
-//        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());
-//            }
-//        }
+        if (authenticationContext.getAuthenticationResult() != null
+                && profileConfiguration.isProxiedAuthnInstant(profileRequestContext)) {
+            
+            try {
+                if (endUserContext.getUnprocessedIdTokenClaims().getDateClaim("auth_time") != null) {
+                    final Date authnTimeDate = endUserContext.getUnprocessedIdTokenClaims().getDateClaim("auth_time");
+                    log.debug("{} Resetting authentication time to proxied value: {}", getLogPrefix(),
+                           authnTimeDate);
+                    authenticationContext.getAuthenticationResult().setAuthenticationInstant(authnTimeDate.toInstant());
+                } else {
+                    log.debug("{} Unable to reset authentication time, auth_time not present in id_token",
+                            getLogPrefix());
+                } 
+            } catch (final ParseException e) {
+                log.debug("{} Unable to reset authentication time, auth_time could not be parsed from id_token: {}",
+                        getLogPrefix(), e.getMessage());
+            }
+        }
         
     }
 
    
     @Override
     protected Subject populateSubject(@Nonnull final Subject subject) {   
+                
+        if (acrTranslator != null && endUserContext.getUnprocessedIdTokenClaims().getClaim("acr") != null
+                && endUserContext.getUnprocessedIdTokenClaims().getClaim("acr") instanceof String) {
+            final String acr =  (String)endUserContext.getUnprocessedIdTokenClaims().getClaim("acr");
+            final Collection<Principal> translated = acrTranslator.apply(List.of(acr));
+            if (translated != null && !translated.isEmpty()) {
+                subject.getPrincipals().addAll(translated);
+                if (log.isDebugEnabled()) {
+                    log.debug("{} Added translated ACR Principals: {}", getLogPrefix(),
+                            translated.stream().map(Principal::getName).collect(Collectors.toUnmodifiableList()));
+                }
+            }
+        }
         
-        //Add ACR from OIDC request/response
+        if (amrTranslator != null && endUserContext.getUnprocessedIdTokenClaims().getClaim("amr") != null
+                && endUserContext.getUnprocessedIdTokenClaims().getClaim("amr") instanceof Collection) {
+            try {
+                final List<String> amrs = endUserContext.getUnprocessedIdTokenClaims().getStringListClaim("amr");
+                final Collection<Principal> translated = amrTranslator.apply(amrs);
+                if (translated != null && !translated.isEmpty()) {
+                    subject.getPrincipals().addAll(translated);
+                    if (log.isDebugEnabled()) {
+                        log.debug("{} Added translated AMR Principals: {}", getLogPrefix(),
+                                translated.stream().map(Principal::getName).collect(Collectors.toUnmodifiableList()));
+                    }
+                }
+            } catch (final ParseException e) {
+                log.warn("Unable to parse AMR claims", e);
+            }
+            
+        }
 
         // TODO What type of subject?  switch on subject_types_supported and subject_type.
         if (endUserContext.getUnprocessedIdTokenClaims().getSubject() != null) {
@@ -305,18 +357,18 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
     }
     
     /**
-     * TODO FINISH Construct a populated {@link ProxyAuthenticationPrincipal} based on the inbound id_token
-     * response.
+     * Construct a populated {@link ProxyAuthenticationPrincipal} based on the upstream OP (the
+     * issuer of the id_token) and the audiences of the token.
      * 
      * @return a constructed {@link ProxyAuthenticationPrincipal} to include in the {@link Subject}
      */
     @Nonnull private ProxyAuthenticationPrincipal buildProxyPrincipal() {
         
-        final ProxyAuthenticationPrincipal proxied = new ProxyAuthenticationPrincipal();
-       
+        // TODO: Is this useful in the OIDC context?
+        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?
+        // TODO: There is no proxy audience I know of in the OIDC or OAuth spec like there is in saml?
         
         return proxied;
     }
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 61e45a3..c33d401 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -36,9 +36,27 @@
         </property>
         <property name="defaultAuthenticationMethodsLookupStrategy">
             <bean
-                class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction"
+                class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction"
                 p:mappings="#{getObject('shibboleth.authn.oidc.rp.PrincipalProxyRequestMappings')}" />
         </property>
+         <property name="authenticationContextClassReferenceTranslationStrategyLookupStrategy">
+            <bean
+                class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction$LookupFunctionWrapper">
+                <constructor-arg>
+                    <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction"
+                    c:mappings="#{getObject('shibboleth.authn.oidc.rp.PrincipalProxyResponseMappings')}"/>
+                </constructor-arg>
+            </bean>
+        </property>
+         <property name="authenticationMethodsReferencesTranslationStrategyLookupStrategy">
+            <bean
+                class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction$LookupFunctionWrapper">
+                <constructor-arg>
+                    <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction"
+                    c:mappings="#{getObject('shibboleth.authn.oidc.rp.PrincipalProxyResponseMappings')}"/>
+                </constructor-arg>
+            </bean>
+        </property>
     </bean>
 
     
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp-config.xml b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp-config.xml
index ed65094..6608675 100644
--- a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp-config.xml
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp-config.xml
@@ -11,18 +11,4 @@
 
 
 
-    <util:map id="shibboleth.authn.oidc.rp.PrincipalProxyRequestMappings">
-        <entry>
-            <key>
-                <bean parent="shibboleth.SAML2AuthnContextClassRef" c:classRef="http://example.org/ac/classes/mfa" />
-            </key>
-            <list>
-                <bean class="net.shibboleth.oidc.authn.principal.AuthenticationContextClassReferencePrincipal"
-                    c:classRef="https://proxy.example.org/context2" />
-            </list>
-        </entry>
-    </util:map>
-
-
-
 </beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
index ed3f9b6..80e68b1 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
@@ -162,6 +162,9 @@ public abstract class AbstractOIDCTest {
     /** The authentication context (above the nested prc).*/
     protected AuthenticationContext ac;
     
+    /** The RP config.*/
+    protected OIDCAuthorizationConfiguration partyConfig;
+    
     /** 
      * Setup the various contexts.
      * 
@@ -202,7 +205,7 @@ public abstract class AbstractOIDCTest {
         outMsgCtx.addSubcontext(metadataContext);
         
         final RelyingPartyContext partyContext = new RelyingPartyContext();
-        final OIDCAuthorizationConfiguration partyConfig = new OIDCAuthorizationConfiguration();  
+        partyConfig = new OIDCAuthorizationConfiguration();  
         partyContext.setProfileConfig(partyConfig);
         partyConfig.setClientCredential(TestCredentialHelper.createClientSecretCredential(CLIENT_SECRET));
         partyConfig.setTokenEndpointAuthMethods(Set.of("client_secret_basic"));
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 41472d7..857679b 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
@@ -21,6 +21,8 @@ import java.net.InetAddress;
 import java.net.URI;
 import java.net.UnknownHostException;
 import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -79,7 +81,9 @@ import net.shibboleth.idp.plugin.authn.test.flow.AbstractAuthnXmlFlowExecutionTe
 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.idp.saml.authn.principal.AuthenticationMethodPrincipal;
 import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
+import net.shibboleth.oidc.authn.principal.AuthenticationContextClassReferencePrincipal;
 import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
 import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
 import net.shibboleth.oidc.profile.config.OIDCSecurityConfiguration;
@@ -1101,8 +1105,61 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         assertFlowExecutionEnded();
         assertNotNull(prc.getSubcontext(AuthenticationContext.class));
         assertNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
-      
-       
+           
+    }
+    
+    @Test 
+    public void testAuthnFlowFromAuthorizationCallback_Using_HMAC_UserInfo_And_IDToken_Response_WithACRAMRTranslation() 
+            throws Exception {
+        
+        setFlowPath(FLOW);
+        setFlowModelResources(flowResources);
+        setSubflows(subflows);        
+        
+        final Map<String,String> mockProperties = Map.of(
+                "idp.service.clientinfo.failFast","false",
+                "idp.entityID", "http://idp.example.com/");
+        
+        setMockProperties(mockProperties);
+        
+        final MockWebServer mockOPServer = createSimpleServer();
+        // First is token exchange
+        mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+                .setHeader("content-type", "application/json")
+                .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID, "demo_rp2"), CLIENT_SECRET)));
+        // Second is userInfo
+        final var userInfoToken = TestTokenHelper.createHMACSignedUserInfoJWTResponseJSON(
+                OP_ISSUER_ID,CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET);
+        mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+                .setHeader("content-type", "application/jwt")
+                .setBody(userInfoToken.serialize()));
+        mockOPServer.start(9918);
+        
+
+        final var flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+                .createFlowExecution(getFlowDefinition());
+        final ProfileRequestContext prc = populateBasicContextTreeFromAuthnResponse(flowExecution);  
+        // Add mapping functions
+        addACRAndAMRFunctions(prc.getSubcontext(AuthenticationContext.class)
+                .getSubcontext(ProfileRequestContext.class));                
+        
+        updateFlowExecution(flowExecution);
+        
+        //set start view and ending event to transition on.
+        externalContext.setEventId("proceed");
+        setCurrentState("AuthnRequest");       
+        resumeFlow(externalContext);
+        
+        mockOPServer.shutdown();
+        
+        assertStandardSuccessConditions(prc);  
+        // Add checks for added ACRs and AMRs
+        assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class).getSubject()
+                .getPrincipals(AuthenticationContextClassReferencePrincipal.class));
+        assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class).getSubject()
+                .getPrincipals(AuthenticationMethodPrincipal.class));
+        
     }
     
     private ProfileRequestContext populateBasicContextTreeFromAuthnResponse(
@@ -1149,6 +1206,34 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
          return prc;
         
     }
+    
+    /* Add functions to convert ACR and AMR in id_token responses to principals.*/
+    private void addACRAndAMRFunctions(@Nonnull final ProfileRequestContext prc) {
+        final OIDCAuthorizationConfiguration partyConfig = 
+                (OIDCAuthorizationConfiguration) prc.getSubcontext(RelyingPartyContext.class).getProfileConfig();
+        
+        partyConfig.setAuthenticationContextClassReferenceTranslationStrategyLookupStrategy(
+                 context -> acrs -> {                     
+                     final List<Principal> principals = new ArrayList<>();
+                     for (final String acr : acrs) {
+                          if ("urn:mace:incommon:iap:silver".equals(acr)){
+                              principals.add(new AuthnContextClassRefPrincipal("urn:mace:incommon:iap:silver"));
+                          }                     
+                     }
+                     return principals;
+                 });
+        partyConfig.setAuthenticationMethodsReferencesTranslationStrategyLookupStrategy(
+                context -> amrs -> {
+                    final List<Principal> principals = new ArrayList<>();
+                    for (final String amr : amrs) {
+                        if ("pwd".equals(amr)) {
+                            principals.add(new
+                                    AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"));
+                        }
+                    }
+                    return principals;
+                });
+    }
 
     
     /** 
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
index 445ff07..0ae02a7 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
@@ -82,6 +82,8 @@ public final class TestTokenHelper {
                 .claim("nonce", nonce)
                 .claim("azp", clientId)
                 .claim("name",name)
+                .claim("acr","urn:mace:incommon:iap:silver")
+                .claim("amr", List.of("pwd", "otp"))
                 .claim("auth_time", new Date())
                 .issueTime(new Date())
                 .expirationTime(Date.from(Instant.now().plusSeconds(120)))
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationTest.java
index d2cdc8d..091d6e5 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationTest.java
@@ -1,11 +1,30 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
 
-import org.testng.annotations.Test;
-import org.testng.AssertJUnit;
+
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
 
+import java.security.Principal;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -37,11 +56,12 @@ import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
 import net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
-import net.shibboleth.idp.authn.principal.UsernamePrincipal;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.principal.OIDCSubjectIdentifierPrincipal;
 import net.shibboleth.idp.plugin.authn.test.spring.MockApplicationContext;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.saml.authn.principal.AuthenticationMethodPrincipal;
+import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
 import net.shibboleth.oidc.attribute.transcoding.OIDCAttributeTranscoder;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
@@ -87,7 +107,7 @@ public class ValidateOIDCAuthenticationTest  extends AbstractOIDCTest {
         registry.setApplicationContext(new MockApplicationContext());
         registry.initialize();
         
-        AssertJUnit.assertEquals(registry.getDisplayNames(new IdPAttribute("givenName")).size(), 1);
+        assertEquals(registry.getDisplayNames(new IdPAttribute("givenName")).size(), 1);
         
         action.setTranscoderRegistry(new MockReloadableService<AttributeTranscoderRegistry>(registry));
     }
@@ -133,6 +153,8 @@ public class ValidateOIDCAuthenticationTest  extends AbstractOIDCTest {
                 .claim("nonce", "abadnonce")
                 .claim("azp", "https://rp.example.com")
                 .claim("name","jdoe")
+                .claim("acr","urn:mace:incommon:iap:silver")
+                .claim("amr", List.of("pwd", "otp"))
                 .expirationTime(Date.from(Instant.now().plusSeconds(120)))
                 .build());
         
@@ -149,20 +171,62 @@ public class ValidateOIDCAuthenticationTest  extends AbstractOIDCTest {
         setupAttributeFilter();
               
     }
+    
+    @Test
+    public void testSuccess_WithACRsAndAMRs() throws ComponentInitializationException {
+        
+        partyConfig.setAuthenticationContextClassReferenceTranslationStrategyLookupStrategy(
+                context -> acrs -> {     
+                    final List<Principal> principals = new ArrayList<>();
+                    for (final String acr : acrs) {
+                         if ("urn:mace:incommon:iap:silver".equals(acr)){
+                             principals.add(new AuthnContextClassRefPrincipal("urn:mace:incommon:iap:silver"));
+                         }                     
+                    }
+                    return principals;
+                });
+       partyConfig.setAuthenticationMethodsReferencesTranslationStrategyLookupStrategy(
+               context -> amrs -> {
+                   final List<Principal> principals = new ArrayList<>();
+                   for (final String amr : amrs) {
+                       if ("pwd".equals(amr)) {
+                           principals.add(new
+                                   AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"));
+                       }
+                   }
+                   return principals;
+               });
+        
+        action.initialize();
+        final Event result = action.execute(src);
+        
+        assertNull(result);
+        assertNotNull(ac.getAuthenticationResult());
+        assertNotNull(ac.getAuthenticationResult().getSubject());
+        final var subject = ac.getAuthenticationResult().getSubject();
+        assertEquals(subject.getPrincipals(OIDCSubjectIdentifierPrincipal.class).size(), 1);
+        assertEquals(subject.getPrincipals(IdPAttributePrincipal.class).size(), 1);
+        assertEquals(subject.getPrincipals(IdPAttributePrincipal.class)
+                .iterator().next().getName(),"givenName");
+        assertEquals(subject.getPrincipals(AuthnContextClassRefPrincipal.class).size(), 1);
+        assertEquals(subject.getPrincipals(AuthenticationMethodPrincipal.class).size(), 1);
+        
+    }
 
     @Test
     public void testSuccess() throws ComponentInitializationException {
         action.initialize();
         final Event result = action.execute(src);
         
-        AssertJUnit.assertNull(result);
-        AssertJUnit.assertNotNull(ac.getAuthenticationResult());
+        assertNull(result);
+        assertNotNull(ac.getAuthenticationResult());
         assertNotNull(ac.getAuthenticationResult().getSubject());
         final var subject = ac.getAuthenticationResult().getSubject();
-        AssertJUnit.assertEquals(subject.getPrincipals(OIDCSubjectIdentifierPrincipal.class).size(), 1);
-        AssertJUnit.assertEquals(subject.getPrincipals(IdPAttributePrincipal.class).size(), 1);
-        AssertJUnit.assertEquals(subject.getPrincipals(IdPAttributePrincipal.class)
+        assertEquals(subject.getPrincipals(OIDCSubjectIdentifierPrincipal.class).size(), 1);
+        assertEquals(subject.getPrincipals(IdPAttributePrincipal.class).size(), 1);
+        assertEquals(subject.getPrincipals(IdPAttributePrincipal.class)
                 .iterator().next().getName(),"givenName");
+
         
     }
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list