[java-idp-plugin-webauthn] branch main updated: Add flow tests of management flow

Phil Smart philip.smart at jisc.ac.uk
Fri Aug 9 16:15:30 UTC 2024


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

philsmart pushed a commit to branch main
in repository java-idp-plugin-webauthn.

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

The following commit(s) were added to refs/heads/main by this push:
     new 18a344e  Add flow tests of management flow
18a344e is described below

commit 18a344e7b9cac5029bd4b2812e8970ca28a8753c
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Aug 9 17:15:28 2024 +0100

    Add flow tests of management flow
---
 .../webauthn/flow/AbstractWebAuthnFlowTest.java    |   7 ++
 ...ationFlow.java => TestAdminManagementFlow.java} | 105 +++++----------------
 .../authn/webauthn/flow/TestRegistrationFlow.java  |  23 ++++-
 ...minManagementApplicationContextInitializer.java |  58 ++++++++++++
 4 files changed, 108 insertions(+), 85 deletions(-)

diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
index e6bd672..ff7a477 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
@@ -608,6 +608,13 @@ public class AbstractWebAuthnFlowTest extends AbstractFlowTest {
         Assert.assertTrue( flowExecution.isActive(), "The flow execution is not active but it should be");
     }
     
+    /**
+     * Assert that the entire flow execution is inactive; that is, it has ended.
+     */
+    protected void assertFlowExecutionInActive(final FlowExecution flowExecution) {
+        Assert.assertFalse( flowExecution.isActive(), "The flow execution is not active but it should be");
+    }
+    
     protected LocalAttributeMap<Object> addToConversationScopeMap(final Map<String, Object> things){
         final LocalAttributeMap<Object> conversationScope = new LocalAttributeMap<>();
         things.entrySet().forEach(entry -> conversationScope.put(entry.getKey(), entry.getValue()));
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java
similarity index 55%
copy from webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
copy to webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java
index 052e87f..ac30b00 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java
@@ -34,15 +34,14 @@ import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
 import com.yubico.webauthn.data.PublicKeyCredential;
 
 import net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyRemovalInformationFromFormRequest;
-import net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractPublicKeyCredentialAttestationFromFormRequest;
+import net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractUsernameSearchFromFormRequest;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.impl.ExtractPublicKeyCredentialAssertionFromFormRequest;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.shared.collection.Pair;
 
 /**
- * Flow tests for the passwordless flow.
+ * Flow tests for the management admin flow.
  */
 @ContextConfiguration(
         locations = {
@@ -50,29 +49,29 @@ import net.shibboleth.shared.collection.Pair;
                 "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml", },
         initializers = {
                 TestWebAuthnEnvironmentApplicationContextInitializer.class,
-                TestWebAuthnRegistrationApplicationContextInitializer.class
+                TestWebAuthnAdminManagementApplicationContextInitializer.class
                 }
         )
 /**
  * Flow tests for the credential registration flow
  */
-public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{  
+public class TestAdminManagementFlow extends AbstractWebAuthnFlowTest{  
 
     /** Flow ID. */
-    @Nonnull public static final String FLOW_ID = "admin/webauthn-registration";
+    @Nonnull public static final String FLOW_ID = "admin/webauthn-management";
     
     /**
      * Constructor.
      *
      * @param id
      */
-    protected TestRegistrationFlow(final String id) {
+    protected TestAdminManagementFlow(final String id) {
         super(id);
     }
     
     @SuppressWarnings("null")
     @Test
-    public void testRegistrationFlow_RemoveCredential() throws Exception {
+    public void testManagementFlow_RemoveCredential() throws Exception {
         //Register a credential for use and to delete.
         final CredentialRegistration registration = 
                 createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
@@ -82,97 +81,35 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
                 new LocalAttributeMap<>());
         
         doAuthenticationDance(result, registration);
-        assertCurrentStateEquals("DisplayWebAuthnRegistrationView", result.getSecond());
+        assertCurrentStateEquals("UsernameSearchView", result.getSecond());
         
         // Re-set external context to holder
         ExternalContextHolder.setExternalContext(externalContext);
-        setHttpFormRequest("POST", Map.of(ExtractKeyRemovalInformationFromFormRequest.DEFAULT_PARAMETER_NAME,
-                registration.getCredentialIdBase64Url()));
-        externalContext.setEventId("deleteKey");
-        result.getSecond().setCurrentState("DisplayWebAuthnRegistrationView");
+        setHttpFormRequest("POST", Map.of(ExtractUsernameSearchFromFormRequest.DEFAULT_PARAMETER_NAME, USERNAME));
+        externalContext.setEventId("proceed");
+        result.getSecond().setCurrentState("UsernameSearchView");
         result.getSecond().resume(externalContext);
         
-        assertCurrentStateEquals("DisplayWebAuthnRegistrationView", result.getSecond());
-        // Test is has been removed
-        assertEquals(credentialRepo.getCredentialIdsForUsername(USERNAME).size(),0);
-        
-    }
-    
-    /** 
-     * Check to make sure we have guards in place to prevent the user from deleting a credential from another user. The
-     * credential comes from user input on the UI, so we must ensure that belongs to the authenticated user.
-     * @throws Exception on error
-     */
-    @SuppressWarnings("null")
-    @Test
-    public void testRegistrationFlow_RemoveCredentialFromDifferentUser() throws Exception {
-        //Register a credential for use
-        final CredentialRegistration registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
-        credentialRepo.addRegistrationByUsername(USERNAME, registration);
-        
-        // Register another uses credential and try and delete it
-        final CredentialRegistration registrationAnotherUser = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
-        credentialRepo.addRegistrationByUsername("another-user", registrationAnotherUser);
-        
-        final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
-                new LocalAttributeMap<>());
-        
-        doAuthenticationDance(result, registration);
-        assertCurrentStateEquals("DisplayWebAuthnRegistrationView", result.getSecond());
-        
+        assertCurrentStateEquals("ManagementView", result.getSecond());
         // Re-set external context to holder
         ExternalContextHolder.setExternalContext(externalContext);
-        setHttpFormRequest("POST", Map.of(ExtractKeyRemovalInformationFromFormRequest.DEFAULT_PARAMETER_NAME,
-                registrationAnotherUser.getCredentialIdBase64Url()));
+        setHttpFormRequest("POST", Map.of(ExtractKeyRemovalInformationFromFormRequest.DEFAULT_PARAMETER_NAME, 
+                registration.getCredentialIdBase64Url()));
         externalContext.setEventId("deleteKey");
-        result.getSecond().setCurrentState("DisplayWebAuthnRegistrationView");
+        result.getSecond().setCurrentState("ManagementView");
         result.getSecond().resume(externalContext);
         
-        assertCurrentStateEquals("DisplayWebAuthnRegistrationView", result.getSecond());
-        // Test is has been removed
-        assertEquals(credentialRepo.getCredentialIdsForUsername(USERNAME).size(),1);
-        assertEquals(credentialRepo.getCredentialIdsForUsername("another-user").size(),1);
-        
-    }
-    
-    @SuppressWarnings("null")
-    @Test
-    public void testRegistrationFlow_AddCredential() throws Exception {
-        //Register a credential for use for authentication.
-        final CredentialRegistration registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
-        credentialRepo.addRegistrationByUsername(USERNAME, registration);
-        
-        final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
-                new LocalAttributeMap<>());
-        
-        doAuthenticationDance(result, registration);
-        assertCurrentStateEquals("DisplayWebAuthnRegistrationView", result.getSecond());
+        assertCurrentStateEquals("ManagementView", result.getSecond());
         
-        // Re-set external context to holder
+        // Now end flow
         ExternalContextHolder.setExternalContext(externalContext);
-        
-        final ProfileRequestContext prc = getProfileRequestContextFromConversation(result.getSecond());
-        
-        // Get the challenge that was set into the PublicKeyCredentialRequestOptions. 
-        // otherwise we will end up signing a different one, which will provide its own test.
-        final WebAuthnRegistrationContext authnContext = getWebAuthnRegistrationContext(prc);
-        final ByteArray challenge = authnContext.getPublicKeyCredentialCreationOptions().getChallenge();
-        
-        final var attestationResponse = createAttestationReponse(challenge.getBytes());
-        final String attestationResponseJson = jsonMapper.writeValueAsString(attestationResponse);
-        setHttpFormRequest("POST", Map.of(ExtractPublicKeyCredentialAttestationFromFormRequest.DEFAULT_PARAMETER_NAME,
-                attestationResponseJson, 
-                ExtractPublicKeyCredentialAttestationFromFormRequest.DEFAULT_NICKNAME_FIELD_NAME, "new-cred"));
-        externalContext.setEventId("addKey");
-        result.getSecond().setCurrentState("DisplayWebAuthnRegistrationView");
+        externalContext.setEventId("finish");
+        result.getSecond().setCurrentState("ManagementView");
         result.getSecond().resume(externalContext);
+        assertFlowExecutionInActive(result.getSecond());
         
-        assertCurrentStateEquals("DisplayWebAuthnRegistrationView", result.getSecond());
         // Test is has been removed
-        assertEquals(credentialRepo.getCredentialIdsForUsername(USERNAME).size(),2);
+        assertEquals(credentialRepo.getCredentialIdsForUsername(USERNAME).size(),0);
         
     }
     
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
index 052e87f..0d8a498 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
@@ -42,7 +42,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.shared.collection.Pair;
 
 /**
- * Flow tests for the passwordless flow.
+ * Flow tests for the registration admin flow.
  */
 @ContextConfiguration(
         locations = {
@@ -96,6 +96,13 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
         // Test is has been removed
         assertEquals(credentialRepo.getCredentialIdsForUsername(USERNAME).size(),0);
         
+        // Now end flow
+        ExternalContextHolder.setExternalContext(externalContext);
+        externalContext.setEventId("finish");
+        result.getSecond().setCurrentState("DisplayWebAuthnRegistrationView");
+        result.getSecond().resume(externalContext);
+        assertFlowExecutionInActive(result.getSecond());
+        
     }
     
     /** 
@@ -135,6 +142,13 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
         assertEquals(credentialRepo.getCredentialIdsForUsername(USERNAME).size(),1);
         assertEquals(credentialRepo.getCredentialIdsForUsername("another-user").size(),1);
         
+        // Now end flow
+        ExternalContextHolder.setExternalContext(externalContext);
+        externalContext.setEventId("finish");
+        result.getSecond().setCurrentState("DisplayWebAuthnRegistrationView");
+        result.getSecond().resume(externalContext);
+        assertFlowExecutionInActive(result.getSecond());
+        
     }
     
     @SuppressWarnings("null")
@@ -174,6 +188,13 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
         // Test is has been removed
         assertEquals(credentialRepo.getCredentialIdsForUsername(USERNAME).size(),2);
         
+        // Now end flow
+        ExternalContextHolder.setExternalContext(externalContext);
+        externalContext.setEventId("finish");
+        result.getSecond().setCurrentState("DisplayWebAuthnRegistrationView");
+        result.getSecond().resume(externalContext);
+        assertFlowExecutionInActive(result.getSecond());
+        
     }
     
     @SuppressWarnings("null")
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnAdminManagementApplicationContextInitializer.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnAdminManagementApplicationContextInitializer.java
new file mode 100644
index 0000000..a81231c
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnAdminManagementApplicationContextInitializer.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed 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.plugin.authn.webauthn.flow;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.mock.env.MockPropertySource;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An {@link ApplicationContextInitializer} which prepends properties.
+ *
+ * <p>This needs to include the original IdP-test-layer properties and has to be
+ * set to {@link Ordered#LOWEST_PRECEDENCE} or things blow up.</p>
+ */
+ at Order(Ordered.LOWEST_PRECEDENCE)
+public class TestWebAuthnAdminManagementApplicationContextInitializer
+        implements ApplicationContextInitializer<ConfigurableApplicationContext> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(TestWebAuthnAdminManagementApplicationContextInitializer.class);
+
+    /** {@inheritDoc} */
+    @Override public void initialize(@Nonnull final ConfigurableApplicationContext applicationContext) {
+        final MockPropertySource mock = new MockPropertySource("passwordless-mock-properties");
+        mock.setProperty("idp.authn.webauthn.usernameless.enabled", "false");
+        // Turn off sessions, less flow complexity
+        mock.setProperty("idp.session.enabled", "false");        
+        mock.setProperty("idp.storage.htmlLocalStorage", "false");
+        // Enable its own WebAuthn authn
+        mock.setProperty("idp.authn.flows", "WebAuthn");
+        // Remove access checks
+        mock.setProperty("idp.authn.webauthn.admin.management.accessPolicy", "AccessByIPAddress");
+        // Don't require special authentication
+        mock.setProperty("idp.authn.webauthn.admin.management.defaultAuthenticationMethods", "");
+        applicationContext.getEnvironment().getPropertySources().addFirst(mock);
+        log.info("Prepending usernameless properties '{}'", mock.getSource());
+    }
+    
+}

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


More information about the commits mailing list