[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