[java-idp-plugin-duo] branch dev/JDUO-82 updated: Add Spring Webflow flow test for paswordless up to initial view

Phil Smart philip.smart at jisc.ac.uk
Mon Jan 8 16:51:06 UTC 2024


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

philsmart pushed a commit to branch dev/JDUO-82
in repository java-idp-plugin-duo.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-duo.git;a=commit;h=a5ac0e8f1eeb4849d3f5def3236a082eac20a605

The following commit(s) were added to refs/heads/dev/JDUO-82 by this push:
     new a5ac0e8f Add Spring Webflow flow test for paswordless up to initial view
a5ac0e8f is described below

commit a5ac0e8f1eeb4849d3f5def3236a082eac20a605
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Jan 8 16:51:04 2024 +0000

    Add Spring Webflow flow test for paswordless up to initial view
---
 idp-duo-impl/pom.xml                               |  16 +++
 .../impl/AbstractAuthnXmlFlowExecutionTests.java   |  10 ++
 .../plugin/authn/duo/impl/DuoAuthnFlowTest.java    | 133 ++++++++++++++++++++-
 pom.xml                                            |  17 ++-
 4 files changed, 173 insertions(+), 3 deletions(-)

diff --git a/idp-duo-impl/pom.xml b/idp-duo-impl/pom.xml
index f22bed15..95285957 100644
--- a/idp-duo-impl/pom.xml
+++ b/idp-duo-impl/pom.xml
@@ -221,6 +221,22 @@
            <artifactId>shib-spring</artifactId>
            <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>mockwebserver</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>junit</groupId>
+                    <artifactId>junit</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp-tls</artifactId>
+            <scope>test</scope>
+        </dependency>
         <!-- Spring webflow tests require Junit4, runs in TestNG bridge -->
         <dependency>
             <groupId>junit</groupId>
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
index 8398e4c3..2bddd262 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
@@ -27,12 +27,14 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.net.ssl.SSLContext;
 
+import org.apache.hc.client5.http.SchemePortResolver;
 import org.apache.hc.client5.http.impl.classic.HttpClients;
 import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
 import org.apache.hc.client5.http.io.HttpClientConnectionManager;
 import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
 import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
 import org.apache.hc.client5.http.ssl.TrustAllStrategy;
+import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.ssl.SSLContexts;
 import org.mockito.Mockito;
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -480,6 +482,7 @@ public abstract class AbstractAuthnXmlFlowExecutionTests extends CustomAbstractX
         try {
             // Create a HttpClient which turns off hostname verification and trusts all certificates (for TESTS!)
             // Would be use either by non-browser or admin API lookups
+            // Switch HTTPS (all) ports to 9191 so we can use a mock server to serve requests.
             final SSLContext sslcontext = SSLContexts.custom()
                     .loadTrustMaterial(null, new TrustAllStrategy())
                     .build();
@@ -489,9 +492,16 @@ public abstract class AbstractAuthnXmlFlowExecutionTests extends CustomAbstractX
             final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
                     .setSSLSocketFactory(sslSocketFactory)
                     .build();
+            
             addBeanSingleton(builderContext, "shibboleth.InternalHttpClient",
                     Constraint.isNotNull(HttpClients.custom()
                     .setConnectionManager(cm)
+                    .setSchemePortResolver(new SchemePortResolver() {                        
+                        @Override
+                        public int resolve(final HttpHost host) {
+                            return 9191;
+                        }
+                    })
                     .evictExpiredConnections()
                     .build(),"HttpClient can not be null"));
            
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
index 5c6a07a3..1f117e4e 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
@@ -15,6 +15,8 @@
 package net.shibboleth.idp.plugin.authn.duo.impl;
 
 
+import java.io.IOException;
+import java.net.UnknownHostException;
 import java.security.Principal;
 import java.util.List;
 import java.util.Map;
@@ -44,6 +46,7 @@ import net.shibboleth.idp.plugin.authn.duo.DefaultDuoOIDCIntegration;
 import net.shibboleth.idp.plugin.authn.duo.DuoClientException;
 import net.shibboleth.idp.plugin.authn.duo.DuoOIDCIntegration;
 import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoPasswordlessContext;
 import net.shibboleth.idp.plugin.authn.mock.MockDuoOIDCClientFactory_FAIL_Client;
 import net.shibboleth.idp.plugin.authn.mock.MockDuoOIDCClientFactory_OK_Client;
 import net.shibboleth.idp.plugin.authn.mock.MockDuoOIDCClient_OK;
@@ -54,6 +57,10 @@ import net.shibboleth.profile.context.RelyingPartyContext;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.component.ComponentInitializationException;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.tls.HandshakeCertificates;
+import okhttp3.tls.HeldCertificate;
 
 /**
  * Test the Duo 2FA flow using SWF flow testing.
@@ -92,6 +99,38 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
                     "classpath:/net/shibboleth/idp/flows/authn/authn-abstract-flow.xml","authn.abstract",
                     "classpath:/net/shibboleth/idp/module/conf/authn/authn-events-flow.xml","authn.events");
     
+    /** A normal response.*/
+    private static final String ADMIN_API_USER_RESPONSE = 
+            """
+            {
+            "response": [
+                {
+                "created": 1592986174,               
+                "email": "",
+                "firstname": null,                
+                "is_enrolled": true,
+                "last_directory_sync": null,
+                "last_login": 1704368292,
+                "lastname": null,
+                "lockout_reason": null,
+                "realname": "",
+                "status": "active",
+                "tokens": [],                
+                "user_id": "FWREGFEROGJER",
+                "username": "jdoe",
+                "webauthncredentials": [
+                    {
+                        "credential_name": "Security Key",
+                        "date_added": 1643124774,
+                        "label": "Passkey",
+                        "webauthnkey": "DDFKJNBVWFRFFDD"
+                    }
+                ]
+                }
+            ],
+            "stat": "OK"
+            }          
+            """;    
     
     /** Constructor.*/
     public DuoAuthnFlowTest() {
@@ -105,7 +144,44 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         loadBeanDefinitionsFromXmlFile(builderContext, 
                 new ClassPathResource("META-INF/net.shibboleth.idp/postconfig.xml"),
-                null);               
+                null);        
+    }
+    
+    /**
+     * Create a running server that mimics responses from the Duo Admin or Auth APIs.
+     * Creates a new self-signed certificate.
+     * 
+     * @return the simple server.
+     * 
+     * @throws UnknownHostException on error.
+     */
+    protected MockWebServer createSimpleServer() throws UnknownHostException {
+        //start mock server
+        final MockWebServer mockServer = new MockWebServer();
+        final HeldCertificate localhostCertificate = new HeldCertificate.Builder()
+            .addSubjectAlternativeName("localhost")
+            .build();
+        final HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
+                .heldCertificate(localhostCertificate)
+                .build();
+        mockServer.useHttps(serverCertificates.sslSocketFactory(), false);      
+        
+        return mockServer;
+    }
+    
+    /**
+     * Queue a mock response. Simulating a response from Duo APIs.
+     * 
+     * @param mockOPServer the mock server
+     * @param code the response HTTP code
+     * @param body the response body
+     * @param contentType the content type header
+     */
+    private void queueMockServerResponse(final MockWebServer mockOPServer, final int code, 
+            final String body, final String contentType) {
+        mockOPServer.enqueue(new MockResponse().setResponseCode(code)
+                .setHeader("content-type", contentType)
+                .setBody(body));
     }
 
     /** Test the Duo flow when the health check returns unhealthy.*/
@@ -172,6 +248,61 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
 
     }
     
+    /** 
+     * Test the Duo flow up to the external authorization request when using the passwordless flow.
+     * 
+     * @throws IOException on error
+     */
+    @Test
+    public void testDuoAuthnFlowToAuthorizationRequestForPasswordless() throws IOException {
+        
+        
+        // Requires mock server to handle Admin API calls
+        final MockWebServer mockServer = createSimpleServer();
+        // Simulate Admin API user response.
+        queueMockServerResponse(mockServer, 200, ADMIN_API_USER_RESPONSE, "application/json");
+
+        
+        mockServer.start(9191); 
+              
+        setFlowPath(FLOW);
+        setFlowModelResources(flowResources);
+        setSubflows(subflows);
+        setClientFactory(new MockDuoOIDCClientFactory_OK_Client());
+        
+       final Map<String,String> mockProperties = Map.of(
+                "idp.duo.oidc.redirectURL","http://localhost/authorization-callback",
+                "idp.duo.oidc.apiHost","api-c9f24c5a.duosecurity.com",
+                "idp.duo.oidc.clientId","DIU6GEFWG5LIUBVV2M3P",
+                "idp.duo.oidc.secretKey","rFvDfPul27v3Wew2zb6xRPzAJewJ34MP2w8UitPh",
+                "idp.duo.oidc.admin.apiHost","localhost",
+                "idp.duo.oidc.admin.integrationKey","DIU6GEFWG5LIUBVV2M3PPPP",
+                "idp.duo.oidc.admin.secretKey","rFvDfPul27v3Wew2zb6xRPzAJewJ34MP2w8UitPh",
+                "idp.duo.oidc.passwordless.allowedLabels","Passkey",
+                "idp.duo.oidc.user.config","duo-oidc-authn-config.xml",
+                "idp.duo.oidc.clientFactoryBean","shibboleth.authn.DuoOIDC.test.clientFactory");
+        
+        setMockProperties(mockProperties);
+        
+        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<>();
+        inputMap.put("calledAsSubflow", true);
+       
+        final ProfileRequestContext context = buildProfileRequestContext(false,true);
+        // Add passwordless context to init passwordless flow
+        final DuoPasswordlessContext passwordlessContext = new DuoPasswordlessContext();
+        context.getSubcontext(AuthenticationContext.class).addSubcontext(passwordlessContext);
+        // Add a username into the request for the passwordless flow to use
+        mockRequest.addParameter("j_username", "jdoe");
+        
+        final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
+        flowExecution.getConversationScope().put("opensamlProfileRequestContext", context);
+        updateFlowExecution(flowExecution);         
+        flowExecution.start(inputMap, externalContext);         
+        assertFlowExecutionActive();
+        assertCurrentStateEquals("PasswordlessView");
+
+    }
+    
     /** Test the Duo flow up to the external authorization request using the dynamically selected
      * first Duo integration.*/
     @Test
diff --git a/pom.xml b/pom.xml
index 19d18657..88662510 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,8 @@
         <idp.groupId>net.shibboleth.idp</idp.groupId>
         <idp.version>5.0.0</idp.version>
         <duo.client.version>1.1.3</duo.client.version>
+        <okhttp3.mockserver.version>4.9.3</okhttp3.mockserver.version>
+        <okhttp3.tls.version>4.9.3</okhttp3.tls.version>
         <opensaml.groupId>org.opensaml</opensaml.groupId>
         <opensaml.version>5.0.0</opensaml.version>
         <oidc-common.groupId>net.shibboleth.oidc</oidc-common.groupId>
@@ -119,8 +121,19 @@
                 <artifactId>idp-plugin-duo-api</artifactId>
                 <version>${project.version}</version>
             </dependency>
-            <!-- test bom dependencies -->
-           
+            <!-- test dependencies -->
+            <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>mockwebserver</artifactId>
+                <version>${okhttp3.mockserver.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>okhttp-tls</artifactId>
+                <version>${okhttp3.tls.version}</version>
+                <scope>test</scope>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

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


More information about the commits mailing list