[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