[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