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

Scott Cantor cantor.2 at osu.edu
Thu Nov 14 19:41:19 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=bebfd4fcca364715d0f6f2214aa70739e17c5cc6

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

commit bebfd4fcca364715d0f6f2214aa70739e17c5cc6
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Nov 14 19:41:11 2019 -0500

    IDP-1494 - Login flow for proxied SAML authentication
    
    https://issues.shibboleth.net/jira/browse/IDP-1494
    
    Add RequestedAuthnContext support and unit test for request action.
---
 .../system/conf/relying-party-mddriven.xml         |  12 +-
 ...tMetadataDrivenConfigurationLookupStrategy.java |  37 +++-
 ...yAwareAuthnContextComparisonLookupFunction.java |  57 ++++++
 ...DefaultAuthenticationMethodsLookupFunction.java |  66 ++++++
 .../config/BrowserSSOProfileConfiguration.java     |   6 +-
 .../saml/saml2/profile/impl/AddAuthnRequest.java   |  70 +++++++
 .../saml2/profile/impl/AddAuthnRequestTest.java    | 225 +++++++++++++++++++++
 7 files changed, 459 insertions(+), 14 deletions(-)

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 3a1ce24..3fe3353 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
@@ -411,11 +411,19 @@
             </bean>
         </property>
         <property name="authnContextComparisonLookupStrategy">
-            <bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="authnContextComparison" />
+            <bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="authnContextComparison">
+                <property name="defaultValueStrategy">
+                    <bean class="net.shibboleth.idp.saml.profile.config.navigate.ProxyAwareAuthnContextComparisonLookupFunction" />
+                </property>
+            </bean>
         </property>
         <property name="defaultAuthenticationMethodsLookupStrategy">
             <bean parent="shibboleth.MDDrivenListProperty" p:propertyName="defaultAuthenticationMethods"
-                p:propertyType="#{T(net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal)}" />
+                    p:propertyType="#{T(net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal)}">
+                <property name="defaultValueStrategy">
+                    <bean class="net.shibboleth.idp.saml.profile.config.navigate.ProxyAwareDefaultAuthenticationMethodsLookupFunction" />
+                </property>
+            </bean>
         </property>
         <property name="postAuthenticationFlowsLookupStrategy">
             <bean parent="shibboleth.MDDrivenListProperty" p:propertyName="postAuthenticationFlows" />
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractMetadataDrivenConfigurationLookupStrategy.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractMetadataDrivenConfigurationLookupStrategy.java
index 4ee321e..03a877b 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractMetadataDrivenConfigurationLookupStrategy.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractMetadataDrivenConfigurationLookupStrategy.java
@@ -57,6 +57,7 @@ import net.shibboleth.utilities.java.support.component.AbstractInitializableComp
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.logic.FunctionSupport;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 /**
@@ -107,7 +108,7 @@ public abstract class AbstractMetadataDrivenConfigurationLookupStrategy<T> exten
     @NonnullAfterInit @NonnullElements private Collection<String> propertyAliases;
     
     /** Optional default to return in the absence of a property. */
-    @Nullable private T defaultValue;
+    @Nullable private Function<BaseContext,T> defaultValueStrategy;
     
     /** Strategy for obtaining metadata to check. */
     @Nullable private Function<BaseContext,EntityDescriptor> metadataLookupStrategy;
@@ -118,6 +119,7 @@ public abstract class AbstractMetadataDrivenConfigurationLookupStrategy<T> exten
     /** Constructor. */
     public AbstractMetadataDrivenConfigurationLookupStrategy() {
         enableCaching = true;
+        defaultValueStrategy = FunctionSupport.constant(null);
     }
     
     /**
@@ -195,7 +197,20 @@ public abstract class AbstractMetadataDrivenConfigurationLookupStrategy<T> exten
     public void setDefaultValue(@Nullable final T value) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
-        defaultValue = value;
+        defaultValueStrategy = FunctionSupport.constant(value);
+    }
+    
+    /**
+     * Sets a default value function to apply in the absence of an explicit property.
+     * 
+     * @param strategy default function to apply
+     * 
+     * @since 4.0.0
+     */
+    public void setDefaultValueStrategy(@Nonnull final Function<BaseContext,T> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        defaultValueStrategy = Constraint.isNotNull(strategy, "Default value strategy cannot be null");
     }
     
     /**
@@ -261,8 +276,8 @@ public abstract class AbstractMetadataDrivenConfigurationLookupStrategy<T> exten
         }
             
         if (entity == null) {
-            log.debug("No metadata available for relying party, default returned for '{}'", propertyName);
-            return defaultValue;
+            log.debug("No metadata available for relying party, applying default strategy for '{}'", propertyName);
+            return defaultValueStrategy.apply(input);
         }
         
         if (profileIdLookupStrategy != null) {
@@ -300,11 +315,12 @@ public abstract class AbstractMetadataDrivenConfigurationLookupStrategy<T> exten
         }
         
         if (ignoreUnmappedEntityAttributes) {
-            log.debug("No applicable mapped tag, default returned for '{}'", propertyName);
+            log.debug("No applicable mapped tag, applying default strategy for '{}'", propertyName);
+            final T ret = defaultValueStrategy.apply(input);
             if (enableCaching) {
-                cacheContext.getPropertyMap().put(propertyName, defaultValue);
+                cacheContext.getPropertyMap().put(propertyName, ret);
             }
-            return defaultValue;
+            return ret;
         }
         
         // Look for "primary" tag name based on profile/property.
@@ -331,11 +347,12 @@ public abstract class AbstractMetadataDrivenConfigurationLookupStrategy<T> exten
             }
         }
         
-        log.debug("No applicable tag, default returned for '{}'", propertyName);
+        log.debug("No applicable tag, applying default strategy for '{}'", propertyName);
+        final T ret = defaultValueStrategy.apply(input);
         if (enableCaching) {
-            cacheContext.getPropertyMap().put(propertyName, defaultValue);
+            cacheContext.getPropertyMap().put(propertyName, ret);
         }
-        return defaultValue;
+        return ret;
     }
 // Checkstyle: CyclomaticComplexity|MethodLength ON
     
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/profile/config/navigate/ProxyAwareAuthnContextComparisonLookupFunction.java
new file mode 100644
index 0000000..83986f9
--- /dev/null
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/ProxyAwareAuthnContextComparisonLookupFunction.java
@@ -0,0 +1,57 @@
+/*
+ * 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.profile.config.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
+
+/**
+ * Implements a set of default logic for determining the {@link org.opensaml.saml.saml2.core.RequestedAuthnContext}
+ * operator to use. 
+ * 
+ * <p>This operates in two different scenarios: ordinary use and proxy SAML authentication use, detectable
+ * by whether the input context is parent-less (the former), or the child of an {@link AuthenticationContext}.</p>
+ * 
+ * <p>In normal use, the value returned is null.</p>
+ * 
+ * <p>In proxy use, the value returned is null unless the parent context itself contains a child context carrying
+ * a particular value. In other words, the proxy default is "passthrough" of the value.</p>
+ * 
+ * @since 4.0.0
+ */
+public class ProxyAwareAuthnContextComparisonLookupFunction implements Function<ProfileRequestContext,String> {
+    
+    /** {@inheritDoc} */
+    @Nullable public String 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.getOperator();
+            }
+        }
+        
+        return null;
+    }
+
+}
\ No newline at end of file
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/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunction.java
new file mode 100644
index 0000000..4941ded
--- /dev/null
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/ProxyAwareDefaultAuthenticationMethodsLookupFunction.java
@@ -0,0 +1,66 @@
+/*
+ * 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.profile.config.navigate;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+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;
+
+/**
+ * Implements a set of default logic for determining the custom principals to derive the
+ * {@link org.opensaml.saml.saml2.core.RequestedAuthnContext} from.
+ * 
+ * <p>This operates in two different scenarios: ordinary use and proxy SAML authentication use, detectable
+ * by whether the input context is parent-less (the former), or the child of an {@link AuthenticationContext}.</p>
+ * 
+ * <p>In normal use, the value returned is empty.</p>
+ * 
+ * <p>In proxy use, the value returned is empty unless the parent context itself contains a child context carrying
+ * particular values. In other words, the proxy default is "passthrough" of the values.</p>
+ * 
+ * @since 4.0.0
+ */
+public class ProxyAwareDefaultAuthenticationMethodsLookupFunction
+        implements Function<ProfileRequestContext,Collection<AuthnContextClassRefPrincipal>> {
+    
+    /** {@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()
+                        .filter(AuthnContextClassRefPrincipal.class::isInstance)
+                        .map(AuthnContextClassRefPrincipal.class::cast)
+                        .collect(Collectors.toUnmodifiableList());
+            }
+        }
+        
+        return Collections.emptyList();
+    }
+
+}
\ No newline at end of file
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 3f9765d..697dbe6 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,6 +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.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
@@ -134,8 +136,8 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
         allowDelegationPredicate = Predicates.alwaysFalse();
         authenticationFlowsLookupStrategy = FunctionSupport.constant(null);
         postAuthenticationFlowsLookupStrategy = FunctionSupport.constant(null);
-        authnContextComparisonLookupStrategy = FunctionSupport.constant(null);
-        defaultAuthenticationContextsLookupStrategy = FunctionSupport.constant(null);
+        authnContextComparisonLookupStrategy = new ProxyAwareAuthnContextComparisonLookupFunction();
+        defaultAuthenticationContextsLookupStrategy = new ProxyAwareDefaultAuthenticationMethodsLookupFunction();
         nameIDFormatPrecedenceLookupStrategy = FunctionSupport.constant(null);
     }
     
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnRequest.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnRequest.java
index 32777eb..676dd3b 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnRequest.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnRequest.java
@@ -17,9 +17,11 @@
 
 package net.shibboleth.idp.saml.saml2.profile.impl;
 
+import java.security.Principal;
 import java.time.Instant;
 import java.util.List;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -28,6 +30,8 @@ import net.shibboleth.idp.authn.AbstractAuthenticationAction;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 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.saml2.profile.config.BrowserSSOProfileConfiguration;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -43,9 +47,11 @@ import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.saml.common.SAMLObjectBuilder;
 import org.opensaml.saml.common.SAMLVersion;
+import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
 import org.opensaml.saml.saml2.core.AuthnRequest;
 import org.opensaml.saml.saml2.core.Issuer;
 import org.opensaml.saml.saml2.core.NameIDPolicy;
+import org.opensaml.saml.saml2.core.RequestedAuthnContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -215,6 +221,9 @@ public class AddAuthnRequest extends AbstractAuthenticationAction {
         final SAMLObjectBuilder<NameIDPolicy> nipBuilder =
                 (SAMLObjectBuilder<NameIDPolicy>) bf.<NameIDPolicy>getBuilderOrThrow(
                         NameIDPolicy.DEFAULT_ELEMENT_NAME);
+        final SAMLObjectBuilder<RequestedAuthnContext> racBuilder =
+                (SAMLObjectBuilder<RequestedAuthnContext>) bf.<RequestedAuthnContext>getBuilderOrThrow(
+                        RequestedAuthnContext.DEFAULT_ELEMENT_NAME);
 
         final AuthnRequest object = requestBuilder.buildObject();
         
@@ -257,8 +266,69 @@ public class AddAuthnRequest extends AbstractAuthenticationAction {
         }
         
         object.setNameIDPolicy(nip);
+
+        final RequestedAuthnContext rac = getRequestedAuthnContext(profileRequestContext, racBuilder);
+        if (rac != null) {
+            final AuthnContextComparisonTypeEnumeration operator =
+                    profileConfiguration.getAuthnContextComparison(profileRequestContext);
+            if (operator != null) {
+                rac.setComparison(operator);
+            }
+            object.setRequestedAuthnContext(rac);
+        }
         
         profileRequestContext.getOutboundMessageContext().setMessage(object);
     }
     
+    /**
+     * Build a {@link RequestedAuthnContext} if warranted.
+     * 
+     * @param profileRequestContext current profile request context
+     * @param builder object builder
+     * 
+     * @return the object to include in the request, or null
+     */
+    @Nullable private RequestedAuthnContext getRequestedAuthnContext(
+            @Nullable final ProfileRequestContext profileRequestContext,
+            @Nonnull final SAMLObjectBuilder<RequestedAuthnContext> builder) {
+        
+        // RequestedAuthnContext also based on profile configuration.
+        final List<Principal> principals = profileConfiguration.getDefaultAuthenticationMethods(profileRequestContext);
+        if (principals.isEmpty()) {
+            return null;
+        }
+        
+        // Check for class refs.
+        final List<AuthnContextClassRefPrincipal> classRefPrincipals = principals.stream()
+                .filter(AuthnContextClassRefPrincipal.class::isInstance)
+                .map(AuthnContextClassRefPrincipal.class::cast)
+                .collect(Collectors.toUnmodifiableList());
+        if (!classRefPrincipals.isEmpty()) {
+            final RequestedAuthnContext rac = builder.buildObject();
+            
+            rac.getAuthnContextClassRefs().addAll(
+                    classRefPrincipals.stream()
+                        .map(AuthnContextClassRefPrincipal::getAuthnContextClassRef)
+                        .collect(Collectors.toUnmodifiableList()));
+            return rac;
+        }
+        
+        // Check for decl refs.
+        final List<AuthnContextDeclRefPrincipal> declRefPrincipals = principals.stream()
+                .filter(AuthnContextDeclRefPrincipal.class::isInstance)
+                .map(AuthnContextDeclRefPrincipal.class::cast)
+                .collect(Collectors.toUnmodifiableList());
+        if (!declRefPrincipals.isEmpty()) {
+            final RequestedAuthnContext rac = builder.buildObject();
+            
+            rac.getAuthnContextDeclRefs().addAll(
+                    declRefPrincipals.stream()
+                        .map(AuthnContextDeclRefPrincipal::getAuthnContextDeclRef)
+                        .collect(Collectors.toUnmodifiableList()));
+            return rac;
+        }
+        
+        return null;
+    }
+    
 }
\ No newline at end of file
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnRequestTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnRequestTest.java
new file mode 100644
index 0000000..e01926a
--- /dev/null
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnRequestTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.impl;
+
+import static org.testng.Assert.*;
+
+import java.util.Arrays;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
+import net.shibboleth.idp.profile.ActionTestingSupport;
+import net.shibboleth.idp.profile.IdPEventIds;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.profile.RequestContextBuilder;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+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.idp.saml.saml2.profile.SAML2ActionTestingSupport;
+import net.shibboleth.idp.saml.saml2.profile.config.BrowserSSOProfileConfiguration;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.opensaml.core.OpenSAMLInitBaseTestCase;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.ParentContextLookup;
+import org.opensaml.saml.saml1.core.AuthenticationStatement;
+import org.opensaml.saml.saml2.core.AuthnContext;
+import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.NameIDPolicy;
+import org.opensaml.saml.saml2.core.NameIDType;
+import org.opensaml.saml.saml2.core.RequestedAuthnContext;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** {@link AddAuthnRequest} unit test. */
+public class AddAuthnRequestTest extends OpenSAMLInitBaseTestCase {
+
+    private RequestContext rc;
+    private AuthenticationContext ac;
+    private ProfileRequestContext prc1,prc2;
+    private RelyingPartyContext rpc;
+    private AddAuthnRequest action;
+    
+    @BeforeMethod public void setUp() throws ComponentInitializationException {
+        rc = new RequestContextBuilder().buildRequestContext();
+        prc1 = new WebflowRequestContextProfileRequestContextLookup().apply(rc);
+        ac = prc1.getSubcontext(AuthenticationContext.class, true);
+        prc2 = ac.getSubcontext(ProfileRequestContext.class, true);
+        prc2.setOutboundMessageContext(new MessageContext());
+        
+        rpc = prc2.getSubcontext(RelyingPartyContext.class, true);
+        rpc.setRelyingPartyId(ActionTestingSupport.INBOUND_MSG_ISSUER);
+        final RelyingPartyConfiguration rp = new RelyingPartyConfiguration();
+        rp.setId("mock");
+        rp.setResponderId(ActionTestingSupport.OUTBOUND_MSG_ISSUER);
+        rp.setDetailedErrors(true);
+        rp.initialize();
+        rpc.setConfiguration(rp);
+        rpc.setProfileConfig(new BrowserSSOProfileConfiguration());
+        
+        action = new AddAuthnRequest();
+        action.setProfileContextLookupStrategy(
+                new ChildContextLookup<>(ProfileRequestContext.class).compose(
+                        new ChildContextLookup<>(AuthenticationContext.class).compose(
+                                new WebflowRequestContextProfileRequestContextLookup())));
+        action.setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
+        action.setIssuerLookupStrategy(new ResponderIdLookupFunction());
+        action.initialize();
+    }
+    
+    /** Test that the action errors out properly if there is no relying party context. */
+    @Test public void testNoRelyingPartyContext() {
+        prc2.removeSubcontext(RelyingPartyContext.class);
+
+        final Event event = action.execute(rc);
+        ActionTestingSupport.assertEvent(event, IdPEventIds.INVALID_PROFILE_CONFIG);
+    }
+
+    /** Test that the action errors out properly if there is no context. */
+    @Test public void testNoMessageContext() {
+        prc2.setOutboundMessageContext(null);
+        
+        final Event event = action.execute(rc);
+        ActionTestingSupport.assertEvent(event, EventIds.INVALID_MSG_CTX);
+    }
+
+    /** Test that the action errors out properly if there is a message there. */
+    @Test public void testExistingMessage() {
+        prc2.getOutboundMessageContext().setMessage(SAML2ActionTestingSupport.buildAuthnRequest());
+        
+        final Event event = action.execute(rc);
+        ActionTestingSupport.assertEvent(event, EventIds.INVALID_MSG_CTX);
+    }
+    
+    /** Test that the action works in vanilla form. */
+    @Test public void testSimple() {
+        final Event event = action.execute(rc);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        assertNotNull(prc2.getOutboundMessageContext().getMessage());
+        assertTrue(prc2.getOutboundMessageContext().getMessage() instanceof AuthnRequest);
+
+        final AuthnRequest request = (AuthnRequest) prc2.getOutboundMessageContext().getMessage();
+        assertEquals(request.getIssuer().getValue(), ActionTestingSupport.OUTBOUND_MSG_ISSUER);
+        assertFalse(request.isForceAuthn());
+        assertFalse(request.isPassive());
+        
+        final NameIDPolicy nid = request.getNameIDPolicy();
+        assertNotNull(nid);
+        assertNull(nid.getFormat());
+        assertTrue(nid.getAllowCreate());
+        
+        assertNull(request.getRequestedAuthnContext());
+    }
+
+    /** Test that the action works for ForceAuthn/IsPassive. */
+    @Test public void testFlags() {
+        ac.setIsPassive(true);
+        ac.setForceAuthn(true);
+        
+        Event event = action.execute(rc);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        assertNotNull(prc2.getOutboundMessageContext().getMessage());
+        assertTrue(prc2.getOutboundMessageContext().getMessage() instanceof AuthnRequest);
+
+        final AuthnRequest request = (AuthnRequest) prc2.getOutboundMessageContext().getMessage();
+        assertEquals(request.getIssuer().getValue(), ActionTestingSupport.OUTBOUND_MSG_ISSUER);
+        assertTrue(request.isForceAuthn());
+        assertTrue(request.isPassive());
+        
+        prc2.getOutboundMessageContext().setMessage(null);
+        ((BrowserSSOProfileConfiguration) rpc.getProfileConfig()).setForceAuthn(false);
+
+        event = action.execute(rc);
+        ActionTestingSupport.assertProceedEvent(event);
+        assertFalse(((AuthnRequest) prc2.getOutboundMessageContext().getMessage()).isForceAuthn());
+    }
+
+    /** Test that the action works with a NameID format set. */
+    @Test public void testNameIDFormat() {
+        ((BrowserSSOProfileConfiguration) rpc.getProfileConfig()).setNameIDFormatPrecedence(
+                Arrays.asList(NameIDType.EMAIL, NameIDType.KERBEROS));
+        
+        Event event = action.execute(rc);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        assertNotNull(prc2.getOutboundMessageContext().getMessage());
+        assertTrue(prc2.getOutboundMessageContext().getMessage() instanceof AuthnRequest);
+
+        final AuthnRequest request = (AuthnRequest) prc2.getOutboundMessageContext().getMessage();
+        final NameIDPolicy nid = request.getNameIDPolicy();
+        assertNotNull(nid);
+        assertEquals(nid.getFormat(), NameIDType.EMAIL);
+        assertTrue(nid.getAllowCreate());
+    }
+
+    /** Test that the action works for RequestedAuthnContext. */
+    @Test public void testAuthnContext() {
+        
+        final RequestedPrincipalContext reqctx = ac.getSubcontext(RequestedPrincipalContext.class, true);
+        reqctx.setOperator("exact");
+        reqctx.setRequestedPrincipals(
+                Arrays.asList(new AuthnContextClassRefPrincipal(AuthnContext.KERBEROS_AUTHN_CTX),
+                        new AuthenticationMethodPrincipal(AuthenticationStatement.KERBEROS_AUTHN_METHOD),
+                        new AuthnContextClassRefPrincipal(AuthnContext.X509_AUTHN_CTX)));
+        
+        Event event = action.execute(rc);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        assertNotNull(prc2.getOutboundMessageContext().getMessage());
+        assertTrue(prc2.getOutboundMessageContext().getMessage() instanceof AuthnRequest);
+
+        AuthnRequest request = (AuthnRequest) prc2.getOutboundMessageContext().getMessage();
+        RequestedAuthnContext rac = request.getRequestedAuthnContext();
+        assertNotNull(rac);
+        assertEquals(rac.getComparison(), AuthnContextComparisonTypeEnumeration.EXACT);
+        assertEquals(rac.getAuthnContextClassRefs().size(), 2);
+        assertEquals(rac.getAuthnContextClassRefs().get(0).getAuthnContextClassRef(), AuthnContext.KERBEROS_AUTHN_CTX);
+        assertEquals(rac.getAuthnContextClassRefs().get(1).getAuthnContextClassRef(), AuthnContext.X509_AUTHN_CTX);
+
+        ((BrowserSSOProfileConfiguration) rpc.getProfileConfig()).setAuthnContextComparison(
+                AuthnContextComparisonTypeEnumeration.EXACT);
+        ((BrowserSSOProfileConfiguration) rpc.getProfileConfig()).setDefaultAuthenticationMethods(
+                Arrays.asList(new AuthnContextClassRefPrincipal(AuthnContext.KERBEROS_AUTHN_CTX),
+                        new AuthnContextClassRefPrincipal(AuthnContext.X509_AUTHN_CTX)));
+
+        prc2.getOutboundMessageContext().setMessage(null);
+        event = action.execute(rc);
+        ActionTestingSupport.assertProceedEvent(event);
+
+        request = (AuthnRequest) prc2.getOutboundMessageContext().getMessage();
+        rac = request.getRequestedAuthnContext();
+        assertNotNull(rac);
+        assertEquals(rac.getComparison(), AuthnContextComparisonTypeEnumeration.EXACT);
+        assertEquals(rac.getAuthnContextClassRefs().size(), 2);
+        assertEquals(rac.getAuthnContextClassRefs().get(0).getAuthnContextClassRef(), AuthnContext.KERBEROS_AUTHN_CTX);
+        assertEquals(rac.getAuthnContextClassRefs().get(1).getAuthnContextClassRef(), AuthnContext.X509_AUTHN_CTX);
+    }
+
+}
\ No newline at end of file

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


More information about the commits mailing list