[java-identity-provider] branch master updated: IDP-1494 - Login flow for proxied SAML authentication

Scott Cantor cantor.2 at osu.edu
Fri Nov 15 16:34:50 EST 2019


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch master
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=427683f760751542ec9b7d9937bda31cdfaf9c4b

The following commit(s) were added to refs/heads/master by this push:
       new  427683f   IDP-1494 - Login flow for proxied SAML authentication
427683f is described below

commit 427683f760751542ec9b7d9937bda31cdfaf9c4b
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Nov 15 16:34:46 2019 -0500

    IDP-1494 - Login flow for proxied SAML authentication
    
    https://issues.shibboleth.net/jira/browse/IDP-1494
    
    Implement bidrectional mapping of AuthnContext info.
---
 .../config/AuthenticationProfileConfiguration.java |   3 +-
 .../main/resources/conf/authn/authn-comparison.xml |  48 ++++++++++
 .../conf/authn/proxy/saml-proxy-config.xml         |  11 +++
 .../system/conf/relying-party-mddriven.xml         |  14 ++-
 .../resources/system/conf/relying-party-system.xml |  19 +++-
 .../config/BrowserSSOProfileConfiguration.java     |  51 +++++++++-
 ...yAwareAuthnContextComparisonLookupFunction.java |   2 +-
 ...DefaultAuthenticationMethodsLookupFunction.java |  45 ++++++++-
 .../profile/config/navigate/package-info.java      |  22 +++++
 ...ultAuthenticationMethodsLookupFunctionTest.java | 106 +++++++++++++++++++++
 .../MapDrivenAuthnContextTranslationStrategy.java  |  86 +++++++++++++++++
 .../profile/impl/ValidateSAMLAuthentication.java   |  38 ++++++--
 12 files changed, 423 insertions(+), 22 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/AuthenticationProfileConfiguration.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/AuthenticationProfileConfiguration.java
index afa06d9..17c45d1 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/AuthenticationProfileConfiguration.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/config/AuthenticationProfileConfiguration.java
@@ -20,6 +20,7 @@ package net.shibboleth.idp.authn.config;
 import java.security.Principal;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -43,7 +44,7 @@ public interface AuthenticationProfileConfiguration extends ProfileConfiguration
      */
     @Nonnull @NonnullElements @NotLive @Unmodifiable List<Principal> getDefaultAuthenticationMethods(
             @Nullable final ProfileRequestContext profileRequestContext);
-    
+        
     /**
      * Get the allowable authentication flows for this profile.
      * 
diff --git a/idp-conf/src/main/resources/conf/authn/authn-comparison.xml b/idp-conf/src/main/resources/conf/authn/authn-comparison.xml
index f167b7a..78c915c 100644
--- a/idp-conf/src/main/resources/conf/authn/authn-comparison.xml
+++ b/idp-conf/src/main/resources/conf/authn/authn-comparison.xml
@@ -74,4 +74,52 @@
         <value>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</value>
     </util:list>
     
+    <!--
+    Mappings to transform custom Principals for proxied AuthnRequests.
+    Key is the input, value is a collection of possibly empty outputs.
+    Used by default to transform authentication type values from SP -> Proxied IdPs.
+    -->
+    <util:map id="shibboleth.PrincipalProxyRequestMappings">
+        <entry>
+            <key>
+                <bean parent="shibboleth.SAML2AuthnContextClassRef"
+                    c:classRef="https://sp.example.org/context" />
+            </key>
+            <list>
+                <bean parent="shibboleth.SAML2AuthnContextClassRef"
+                    c:classRef="https://proxy.example.org/context1" />
+                <bean parent="shibboleth.SAML2AuthnContextClassRef"
+                    c:classRef="https://proxy.example.org/context2" />
+            </list>
+        </entry>
+        <entry>
+            <key>
+                <bean parent="shibboleth.SAML1AuthenticationMethod"
+                    c:method="urn:oasis:names:tc:SAML:1.0:am:password" />
+            </key>
+            <list>
+                <bean parent="shibboleth.SAML2AuthnContextClassRef"
+                    c:classRef="urn:mace:osu.edu:shibboleth:ac:classes:basic" />
+            </list>
+        </entry>
+    </util:map>
+
+    <!--
+    Mappings to transform proxied Principals for inclusion in Subjects.
+    Key is the input, value is a collection of possibly empty outputs.
+    Used by default to transform values from Proxied IdP -> 
+    -->
+    <util:map id="shibboleth.PrincipalProxyResponseMappings">
+        <entry>
+            <key>
+                <bean parent="shibboleth.SAML2AuthnContextClassRef"
+                    c:classRef="urn:mace:osu.edu:shibboleth:ac:classes:basic" />
+            </key>
+            <list>
+                <bean parent="shibboleth.SAML1AuthenticationMethod"
+                    c:method="urn:oasis:names:tc:SAML:1.0:am:password" />
+            </list>
+        </entry>
+    </util:map>
+    
 </beans>
diff --git a/idp-conf/src/main/resources/conf/authn/proxy/saml-proxy-config.xml b/idp-conf/src/main/resources/conf/authn/proxy/saml-proxy-config.xml
index 8503c68..72ed977 100644
--- a/idp-conf/src/main/resources/conf/authn/proxy/saml-proxy-config.xml
+++ b/idp-conf/src/main/resources/conf/authn/proxy/saml-proxy-config.xml
@@ -13,4 +13,15 @@
        default-destroy-method="destroy">
 
 
+    <!--
+    Add authentication flow descriptor's supportedPrincipals collection to the
+    resulting Subject? This may be problematic if it happens without regard for
+    the information returned in the assertion from the IdP, so the default is
+    likely to lead to violations of intent.
+    
+    Usually this should be FALSE, and appropriate bidirectional mappings defined
+    via conf/authn/authn-comparison.xml to translate across the proxy boundary.
+    -->
+    <util:constant id="shibboleth.authn.SAML.addDefaultPrincipals" static-field="java.lang.Boolean.TRUE"/>
+
 </beans>
diff --git a/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml b/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml
index 3fe3353..c28d587 100644
--- a/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml
+++ b/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml
@@ -410,10 +410,19 @@
                 <constructor-arg value="true" />
             </bean>
         </property>
+        <property name="authnContextTranslationStrategyLookupStrategy">
+            <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="authnContextTranslationStrategy"
+                    p:propertyType="#{T(java.util.function.Function)}">
+                <property name="defaultValue">
+                    <bean class="net.shibboleth.idp.saml.authn.principal.impl.MapDrivenAuthnContextTranslationStrategy"
+                        p:mappings="#{getObject('shibboleth.PrincipalProxyResponseMappings')}" />
+                </property>
+            </bean>
+        </property>
         <property name="authnContextComparisonLookupStrategy">
             <bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="authnContextComparison">
                 <property name="defaultValueStrategy">
-                    <bean class="net.shibboleth.idp.saml.profile.config.navigate.ProxyAwareAuthnContextComparisonLookupFunction" />
+                    <bean class="net.shibboleth.idp.saml.saml2.profile.config.navigate.ProxyAwareAuthnContextComparisonLookupFunction" />
                 </property>
             </bean>
         </property>
@@ -421,7 +430,8 @@
             <bean parent="shibboleth.MDDrivenListProperty" p:propertyName="defaultAuthenticationMethods"
                     p:propertyType="#{T(net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal)}">
                 <property name="defaultValueStrategy">
-                    <bean class="net.shibboleth.idp.saml.profile.config.navigate.ProxyAwareDefaultAuthenticationMethodsLookupFunction" />
+		            <bean class="net.shibboleth.idp.saml.saml2.profile.config.navigate.ProxyAwareDefaultAuthenticationMethodsLookupFunction"
+		                p:mappings="#{getObject('shibboleth.PrincipalProxyRequestMappings')}" />        
                 </property>
             </bean>
         </property>
diff --git a/idp-conf/src/main/resources/system/conf/relying-party-system.xml b/idp-conf/src/main/resources/system/conf/relying-party-system.xml
index eee31bd..4d51c42 100644
--- a/idp-conf/src/main/resources/system/conf/relying-party-system.xml
+++ b/idp-conf/src/main/resources/system/conf/relying-party-system.xml
@@ -75,11 +75,20 @@
         p:inboundInterceptorFlows="security-policy/saml-soap" />
 
     <bean id="SAML2.SSO" lazy-init="true"
-        class="net.shibboleth.idp.saml.saml2.profile.config.BrowserSSOProfileConfiguration"
-        p:artifactConfiguration-ref="shibboleth.DefaultArtifactConfiguration"
-        p:inboundInterceptorFlows="security-policy/saml2-sso"
-        p:encryptionOptionalPredicate="%{idp.encryption.optional:false}" />
-        
+	        class="net.shibboleth.idp.saml.saml2.profile.config.BrowserSSOProfileConfiguration"
+	        p:artifactConfiguration-ref="shibboleth.DefaultArtifactConfiguration"
+	        p:inboundInterceptorFlows="security-policy/saml2-sso"
+	        p:encryptionOptionalPredicate="%{idp.encryption.optional:false}">
+        <property name="defaultAuthenticationMethodsLookupStrategy">
+            <bean class="net.shibboleth.idp.saml.saml2.profile.config.navigate.ProxyAwareDefaultAuthenticationMethodsLookupFunction"
+                p:mappings="#{getObject('shibboleth.PrincipalProxyRequestMappings')}" />        
+        </property>
+        <property name="authnContextTranslationStrategy">
+            <bean class="net.shibboleth.idp.saml.authn.principal.impl.MapDrivenAuthnContextTranslationStrategy"
+                p:mappings="#{getObject('shibboleth.PrincipalProxyResponseMappings')}" />
+        </property>
+    </bean>
+
     <util:constant id="SAML2.SSO.FEATURE_AUTHNCONTEXT"
         static-field="net.shibboleth.idp.saml.saml2.profile.config.BrowserSSOProfileConfiguration.FEATURE_AUTHNCONTEXT"/>
 
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
index 697dbe6..f9d7824 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
@@ -32,8 +32,8 @@ import javax.annotation.Nullable;
 import net.shibboleth.idp.authn.config.AuthenticationProfileConfiguration;
 import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
 import net.shibboleth.idp.saml.profile.config.logic.ProxyAwareForceAuthnPredicate;
-import net.shibboleth.idp.saml.profile.config.navigate.ProxyAwareAuthnContextComparisonLookupFunction;
-import net.shibboleth.idp.saml.profile.config.navigate.ProxyAwareDefaultAuthenticationMethodsLookupFunction;
+import net.shibboleth.idp.saml.saml2.profile.config.navigate.ProxyAwareAuthnContextComparisonLookupFunction;
+import net.shibboleth.idp.saml.saml2.profile.config.navigate.ProxyAwareDefaultAuthenticationMethodsLookupFunction;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
@@ -44,6 +44,7 @@ import net.shibboleth.utilities.java.support.logic.FunctionSupport;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.saml2.core.AuthnContext;
 import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
 import org.opensaml.saml.saml2.core.SubjectLocality;
 
@@ -94,6 +95,10 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
     /** Lookup function to supply maximum delegation chain length. */
     @Nonnull private Function<ProfileRequestContext,Long> maximumTokenDelegationChainLengthLookupStrategy;
 
+    /** Lookup function to supply the strategy function for translating SAML 2.0 AuthnContext data. */
+    @Nonnull private Function<ProfileRequestContext,Function<AuthnContext,Collection<Principal>>>
+        authnContextTranslationStrategyLookupStrategy;
+    
     /** Lookup function for requested AC operator. */
     @Nonnull private Function<ProfileRequestContext,String> authnContextComparisonLookupStrategy;
     
@@ -136,6 +141,7 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
         allowDelegationPredicate = Predicates.alwaysFalse();
         authenticationFlowsLookupStrategy = FunctionSupport.constant(null);
         postAuthenticationFlowsLookupStrategy = FunctionSupport.constant(null);
+        authnContextTranslationStrategyLookupStrategy = FunctionSupport.constant(null);
         authnContextComparisonLookupStrategy = new ProxyAwareAuthnContextComparisonLookupFunction();
         defaultAuthenticationContextsLookupStrategy = new ProxyAwareDefaultAuthenticationMethodsLookupFunction();
         nameIDFormatPrecedenceLookupStrategy = FunctionSupport.constant(null);
@@ -501,6 +507,47 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
     }
     
     /**
+     * Get the function to use to translate an inbound proxied SAML 2.0 {@link AuthnContext} into the appropriate
+     * set of custom {@link Principal} objects to populate into the subject.
+     * 
+     * @param profileRequestContext current profile request context
+     * 
+     * @return translation function
+     * 
+     * @since 4.0.0
+     */
+    @Nullable public Function<AuthnContext,Collection<Principal>> getAuthnContextTranslationStrategy(
+            @Nullable final ProfileRequestContext profileRequestContext) {
+        return authnContextTranslationStrategyLookupStrategy.apply(profileRequestContext);
+    }
+
+    /**
+     * Set the function to use to translate an inbound proxied SAML 2.0 {@link AuthnContext} into the appropriate
+     * set of custom {@link Principal} objects to populate into the subject.
+     * 
+     * @param strategy translation function
+     * 
+     * @since 4.0.0
+     */
+    public void setAuthnContextTranslationStrategy(@Nullable final Function<AuthnContext,Collection<Principal>> strategy) {
+        authnContextTranslationStrategyLookupStrategy = FunctionSupport.constant(strategy);
+    }
+
+    /**
+     * Set a lookup strategy for the function to use to translate an inbound proxied SAML 2.0 {@link AuthnContext}
+     * into the appropriate set of custom {@link Principal} objects to populate into the subject.
+     * 
+     * @param strategy lookup strategy
+     * 
+     * @since 4.0.0
+     */
+    public void setAuthnContextTranslationStrategyLookupStrategy(
+            @Nullable final Function<ProfileRequestContext,Function<AuthnContext,Collection<Principal>>> strategy) {
+        authnContextTranslationStrategyLookupStrategy =
+                Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+
+    /**
      * Get the comparison operator to use when issuing SAML requests containing requested context classes.
      * 
      * @param profileRequestContext profile request context
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/ProxyAwareAuthnContextComparisonLookupFunction.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/ProxyAwareAuthnContextComparisonLookupFunction.java
similarity index 97%
rename from idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/ProxyAwareAuthnContextComparisonLookupFunction.java
rename to idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/ProxyAwareAuthnContextComparisonLookupFunction.java
index 83986f9..29fe9dc 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/ProxyAwareAuthnContextComparisonLookupFunction.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/ProxyAwareAuthnContextComparisonLookupFunction.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.saml.profile.config.navigate;
+package net.shibboleth.idp.saml.saml2.profile.config.navigate;
 
 import java.util.function.Function;
 
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunction.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunction.java
similarity index 60%
rename from idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunction.java
rename to idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunction.java
index 4941ded..fe89b3f 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunction.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunction.java
@@ -15,13 +15,18 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.saml.profile.config.navigate;
+package net.shibboleth.idp.saml.saml2.profile.config.navigate;
 
+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 java.util.function.Function;
 import java.util.stream.Collectors;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -29,6 +34,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
 import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 
 /**
  * Implements a set of default logic for determining the custom principals to derive the
@@ -47,13 +53,46 @@ import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
 public class ProxyAwareDefaultAuthenticationMethodsLookupFunction
         implements Function<ProfileRequestContext,Collection<AuthnContextClassRefPrincipal>> {
     
+    /** Mappings to transform proxied Principals. */
+    @Nonnull @NonnullElements private Map<Principal,Collection<Principal>> principalMappings;
+    
+    /** Constructor. */
+    public ProxyAwareDefaultAuthenticationMethodsLookupFunction() {
+        principalMappings = Collections.emptyMap();
+    }
+    
+    /**
+     * Sets the mappings from input/proxied Principals to zero or more equivalent values to use.
+     * 
+     * <p>Any values not mapped will be assumed to be passed through.</p>
+     * 
+     * @param mappings {@link Principal} mappings
+     */
+    public void setMappings(@Nullable @NonnullElements final Map<Principal,Collection<Principal>> mappings) {
+        if (mappings == null || mappings.isEmpty()) {
+            principalMappings = Collections.emptyMap();
+            return;
+        }
+        
+        principalMappings = new HashMap<>(mappings.size());
+        mappings.forEach((k, v) -> principalMappings.put(k, List.copyOf(v)));
+    }
+    
     /** {@inheritDoc} */
     @Nullable public Collection<AuthnContextClassRefPrincipal> apply(@Nullable final ProfileRequestContext input) {
         if (input != null && input.getParent() instanceof AuthenticationContext) {
             final RequestedPrincipalContext rpc = input.getParent().getSubcontext(RequestedPrincipalContext.class);
             if (rpc != null) {
-                return rpc.getRequestedPrincipals()
-                        .stream()
+                // Returns a transformed collection of the original principals, replacing any elements
+                // found in the multimap with the corresponding (possibly empty) set of replacements.
+                return rpc.getRequestedPrincipals().stream()
+                        .map(p -> {
+                            if (principalMappings.containsKey(p)) {
+                                return principalMappings.get(p);
+                            }
+                            return Collections.singletonList(p);
+                        })
+                        .flatMap(Collection::stream)
                         .filter(AuthnContextClassRefPrincipal.class::isInstance)
                         .map(AuthnContextClassRefPrincipal.class::cast)
                         .collect(Collectors.toUnmodifiableList());
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/package-info.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/package-info.java
new file mode 100644
index 0000000..397bf99
--- /dev/null
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Functions to navigate into SAML 2 profile configuration information.
+ */
+
+package net.shibboleth.idp.saml.saml2.profile.config.navigate;
\ No newline at end of file
diff --git a/idp-saml-api/src/test/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunctionTest.java b/idp-saml-api/src/test/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunctionTest.java
new file mode 100644
index 0000000..35ef55c
--- /dev/null
+++ b/idp-saml-api/src/test/java/net/shibboleth/idp/saml/saml2/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunctionTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.saml.saml2.profile.config.navigate;
+
+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 java.util.stream.Collectors;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
+import net.shibboleth.idp.profile.RequestContextBuilder;
+import net.shibboleth.idp.saml.authn.principal.AuthenticationMethodPrincipal;
+import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
+import net.shibboleth.idp.saml.authn.principal.AuthnContextDeclRefPrincipal;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.opensaml.core.OpenSAMLInitBaseTestCase;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** Unit test for {@link ProxyAwareDefaultAuthenticationMethodsLookupFunction}. */
+public class ProxyAwareDefaultAuthenticationMethodsLookupFunctionTest extends OpenSAMLInitBaseTestCase {
+    
+    private ProfileRequestContext prc1,prc2;
+    private AuthenticationContext ac;
+    private RequestedPrincipalContext rpc;
+    private ProxyAwareDefaultAuthenticationMethodsLookupFunction fn;
+    
+    @BeforeMethod
+    public void setUp() throws ComponentInitializationException {
+        prc1 = new RequestContextBuilder().buildProfileRequestContext();
+        ac = prc1.getSubcontext(AuthenticationContext.class, true);
+        prc2 = ac.getSubcontext(ProfileRequestContext.class, true);
+        rpc = ac.getSubcontext(RequestedPrincipalContext.class, true);
+        fn = new ProxyAwareDefaultAuthenticationMethodsLookupFunction();
+    }
+    
+    @Test
+    public void testEmptyTree() {
+        final Collection<AuthnContextClassRefPrincipal> principals = fn.apply(prc1);
+        Assert.assertTrue(principals.isEmpty());
+    }
+    
+    @Test
+    public void testNonProxy() {
+        rpc.setRequestedPrincipals(List.of(
+                new AuthnContextClassRefPrincipal("foo"),
+                new AuthnContextClassRefPrincipal("bar")));
+        
+        final Collection<AuthnContextClassRefPrincipal> principals = fn.apply(prc1);
+        Assert.assertTrue(principals.isEmpty());
+    }
+    
+    @Test
+    public void testPassthrough() {
+        rpc.setRequestedPrincipals(List.of(
+                new AuthnContextClassRefPrincipal("foo"),
+                new AuthnContextClassRefPrincipal("bar")));
+        
+        final Collection<AuthnContextClassRefPrincipal> principals = fn.apply(prc2);
+        Assert.assertEquals(principals, rpc.getRequestedPrincipals());
+    }
+
+    @Test
+    public void testMapped() {
+        rpc.setRequestedPrincipals(List.of(
+                new AuthnContextClassRefPrincipal("foo"),
+                new AuthnContextClassRefPrincipal("bar"),
+                new AuthenticationMethodPrincipal("baz")));
+        
+        final Map<Principal,Collection<Principal>> mappings = new HashMap<>();
+        mappings.put(new AuthnContextClassRefPrincipal("foo"), Collections.emptyList());
+        mappings.put(new AuthenticationMethodPrincipal("baz"),
+                List.of(new AuthnContextClassRefPrincipal("frobnitz"),
+                        new AuthnContextDeclRefPrincipal("grue"),
+                        new AuthnContextClassRefPrincipal("zorkmid")));
+        
+        fn.setMappings(mappings);
+        
+        final Collection<AuthnContextClassRefPrincipal> principals = fn.apply(prc2);
+        Assert.assertEquals(principals.stream().map(p -> p.getName()).collect(Collectors.toUnmodifiableList()),
+                List.of("bar", "frobnitz", "zorkmid"));
+    }
+
+}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/authn/principal/impl/MapDrivenAuthnContextTranslationStrategy.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/authn/principal/impl/MapDrivenAuthnContextTranslationStrategy.java
new file mode 100644
index 0000000..b7f85ec
--- /dev/null
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/authn/principal/impl/MapDrivenAuthnContextTranslationStrategy.java
@@ -0,0 +1,86 @@
+/*
+ * 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.saml.authn.principal.impl;
+
+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 java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.saml.saml2.core.AuthnContext;
+
+import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
+import net.shibboleth.idp.saml.authn.principal.AuthnContextDeclRefPrincipal;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+
+/**
+ * Implements a set of default logic for mapping an {@link AuthnContext}'s content into a set of
+ * custom Principals based on a set of static mapping rules.
+ * 
+ * @since 4.0.0
+ */
+public class MapDrivenAuthnContextTranslationStrategy implements Function<AuthnContext,Collection<Principal>> {
+    
+    /** Mappings to transform proxied Principals. */
+    @Nonnull @NonnullElements private Map<Principal,Collection<Principal>> principalMappings;
+    
+    /** Constructor. */
+    public MapDrivenAuthnContextTranslationStrategy() {
+        principalMappings = Collections.emptyMap();
+    }
+    
+    /**
+     * Sets the mappings from input/proxied Principals to zero or more equivalent values to use.
+     * 
+     * <p>Any values not mapped will be assumed to be passed through.</p>
+     * 
+     * @param mappings {@link Principal} mappings
+     */
+    public void setMappings(@Nullable @NonnullElements final Map<Principal,Collection<Principal>> mappings) {
+        if (mappings == null || mappings.isEmpty()) {
+            principalMappings = Collections.emptyMap();
+            return;
+        }
+        
+        principalMappings = new HashMap<>(mappings.size());
+        mappings.forEach((k, v) -> principalMappings.put(k, List.copyOf(v)));
+    }
+    
+    /** {@inheritDoc} */
+    @Nullable public Collection<Principal> apply(@Nullable final AuthnContext input) {
+        
+        if (input != null) {
+            if (input.getAuthnContextClassRef() != null) {
+                return principalMappings.get(new AuthnContextClassRefPrincipal(
+                        input.getAuthnContextClassRef().getAuthnContextClassRef()));
+            } else if (input.getAuthnContextDeclRef() != null) {
+                return principalMappings.get(new AuthnContextDeclRefPrincipal(
+                        input.getAuthnContextDeclRef().getAuthnContextDeclRef()));
+            }
+        }
+        
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ValidateSAMLAuthentication.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ValidateSAMLAuthentication.java
index 0b054e7..2920c5a 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ValidateSAMLAuthentication.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ValidateSAMLAuthentication.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.saml.saml2.profile.impl;
 
+import java.security.Principal;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.function.Function;
@@ -44,6 +45,8 @@ import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
 import net.shibboleth.idp.authn.principal.ProxyAuthenticationPrincipal;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
+import net.shibboleth.idp.saml.authn.principal.AuthnContextDeclRefPrincipal;
 import net.shibboleth.idp.saml.authn.principal.NameIDPrincipal;
 import net.shibboleth.idp.saml.profile.context.navigate.SAMLMetadataContextLookupFunction;
 import net.shibboleth.idp.saml.saml2.profile.config.BrowserSSOProfileConfiguration;
@@ -65,6 +68,7 @@ import org.opensaml.saml.saml2.core.Assertion;
 import org.opensaml.saml.saml2.core.Attribute;
 import org.opensaml.saml.saml2.core.AttributeStatement;
 import org.opensaml.saml.saml2.core.AuthenticatingAuthority;
+import org.opensaml.saml.saml2.core.AuthnContext;
 import org.opensaml.saml.saml2.core.Response;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -117,6 +121,9 @@ public class ValidateSAMLAuthentication extends AbstractValidationAction {
     
     /** Store off profile config. */
     @Nullable private BrowserSSOProfileConfiguration profileConfiguration;
+    
+    /** Incoming context translation function. */
+    @Nullable private Function<AuthnContext,Collection<Principal>> authnContextTranslator;
 
     /** Context for externally supplied inbound attributes. */
     @Nullable private AttributeContext attributeContext;
@@ -270,6 +277,8 @@ public class ValidateSAMLAuthentication extends AbstractValidationAction {
             }
         }
         
+        authnContextTranslator = profileConfiguration.getAuthnContextTranslationStrategy(profileRequestContext);
+        
         buildAuthenticationResult(profileRequestContext, authenticationContext);
         
         if (authenticationContext.getAuthenticationResult() != null
@@ -290,20 +299,33 @@ public class ValidateSAMLAuthentication extends AbstractValidationAction {
         if (samlAuthnContext.getSubject() != null && samlAuthnContext.getSubject().getNameID() != null) {
             subject.getPrincipals().add(new NameIDPrincipal(samlAuthnContext.getSubject().getNameID()));
         }
+
+        final AuthnContext authnContext = samlAuthnContext.getAuthnStatement().getAuthnContext();
+        
+        if (authnContextTranslator != null) {
+            final Collection<Principal> translated = authnContextTranslator.apply(authnContext);
+            if (translated != null) {
+                subject.getPrincipals().addAll(translated);
+            }
+        } else if (authnContext.getAuthnContextClassRef() != null) {
+            subject.getPrincipals().add(new AuthnContextClassRefPrincipal(
+                    authnContext.getAuthnContextClassRef().getAuthnContextClassRef()));
+        } else if (authnContext.getAuthnContextDeclRef() != null) {
+            subject.getPrincipals().add(new AuthnContextDeclRefPrincipal(
+                    authnContext.getAuthnContextDeclRef().getAuthnContextDeclRef()));
+        }
         
         final ProxyAuthenticationPrincipal proxied = new ProxyAuthenticationPrincipal();
         proxied.getAuthorities().add(
                 ((Assertion) samlAuthnContext.getAuthnStatement().getParent()).getIssuer().getValue());
         
-        final Collection<AuthenticatingAuthority> authorities =
-                samlAuthnContext.getAuthnStatement().getAuthnContext().getAuthenticatingAuthorities();
-        if (!authorities.isEmpty()) {
+        if (!authnContext.getAuthenticatingAuthorities().isEmpty()) {
             proxied.getAuthorities().addAll(
-                authorities
-                    .stream()
-                    .map(AuthenticatingAuthority::getURI)
-                    .filter(aa -> !Strings.isNullOrEmpty(aa))
-                    .collect(Collectors.toUnmodifiableList()));
+                    authnContext.getAuthenticatingAuthorities()
+                        .stream()
+                        .map(AuthenticatingAuthority::getURI)
+                        .filter(aa -> !Strings.isNullOrEmpty(aa))
+                        .collect(Collectors.toUnmodifiableList()));
         }
         subject.getPrincipals().add(proxied);
         

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


More information about the commits mailing list