diff --git a/web/src/main/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsFilter.java b/web/src/main/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsFilter.java index 877869b709a..26abf556f29 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsFilter.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsFilter.java @@ -75,6 +75,17 @@ public PublicKeyCredentialRequestOptionsFilter(WebAuthnRelyingPartyOperations rp this.rpOptions = rpOptions; } + /** + * Sets the {@link RequestMatcher} used to trigger this filter. By default, the + * {@link RequestMatcher} is {@code POST /webauthn/authenticate/options}. + * @param requestMatcher the {@link RequestMatcher} to use + * @since 6.5 + */ + public void setRequestMatcher(RequestMatcher requestMatcher) { + Assert.notNull(requestMatcher, "requestMatcher cannot be null"); + this.matcher = requestMatcher; + } + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java b/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java index 0863925c8c8..6c9aa3b75e4 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java @@ -82,6 +82,18 @@ public PublicKeyCredentialCreationOptionsFilter(WebAuthnRelyingPartyOperations r this.rpOperations = rpOperations; } + /** + * Sets the {@link RequestMatcher} used to trigger this filter. + *

+ * By default, the {@link RequestMatcher} is {@code POST /webauthn/register/options}. + * @param requestMatcher the {@link RequestMatcher} to use + * @since 6.5 + */ + public void setRequestMatcher(RequestMatcher requestMatcher) { + Assert.notNull(requestMatcher, "requestMatcher cannot be null"); + this.matcher = requestMatcher; + } + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilter.java b/web/src/main/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilter.java index d14e8559148..834531afc96 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilter.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilter.java @@ -105,6 +105,32 @@ public WebAuthnRegistrationFilter(UserCredentialRepository userCredentials, this.rpOptions = rpOptions; } + /** + * Sets the {@link RequestMatcher} to trigger this filter's the credential + * registration operation . + *

+ * By default, the {@link RequestMatcher} is {@code POST /webauthn/register}. + * @param registerCredentialMatcher the {@link RequestMatcher} to use + * @since 6.5 + */ + public void setRegisterCredentialMatcher(RequestMatcher registerCredentialMatcher) { + Assert.notNull(registerCredentialMatcher, "registerCredentialMatcher cannot be null"); + this.registerCredentialMatcher = registerCredentialMatcher; + } + + /** + * Sets the {@link RequestMatcher} to trigger this filter's the credential removal + * operation . + *

+ * By default, the {@link RequestMatcher} is {@code DELETE /webauthn/register/{id}}. + * @param removeCredentialMatcher the {@link RequestMatcher} to use + * @since 6.5 + */ + public void setRemoveCredentialMatcher(RequestMatcher removeCredentialMatcher) { + Assert.notNull(removeCredentialMatcher, "removeCredentialMatcher cannot be null"); + this.removeCredentialMatcher = removeCredentialMatcher; + } + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { diff --git a/web/src/test/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsFilterTests.java b/web/src/test/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsFilterTests.java index 27b2bef8eae..ff73ad08d9b 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsFilterTests.java @@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets; +import jakarta.servlet.FilterChain; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,10 +31,13 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions; import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions; @@ -48,6 +52,8 @@ import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.verifyNoInteractions; import static org.mockito.BDDMockito.willAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -75,6 +81,10 @@ class PublicKeyCredentialRequestOptionsFilterTests { private PublicKeyCredentialRequestOptionsFilter filter; + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + private MockMvc mockMvc; @BeforeEach @@ -82,6 +92,8 @@ void setup() { this.filter = new PublicKeyCredentialRequestOptionsFilter(this.relyingPartyOperations); this.filter.setRequestOptionsRepository(this.requestOptionsRepository); this.mockMvc = MockMvcBuilders.standaloneSetup().addFilter(this.filter).build(); + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); } @AfterEach @@ -89,6 +101,15 @@ void cleanup() { SecurityContextHolder.clearContext(); } + @Test + void doFilterWhenCustomRequestMatcherThenUses() throws Exception { + RequestMatcher requestMatcher = mock(RequestMatcher.class); + this.filter.setRequestMatcher(requestMatcher); + FilterChain mock = mock(FilterChain.class); + this.filter.doFilter(this.request, this.response, mock); + verify(requestMatcher).matches(any()); + } + @Test void constructorWhenNull() { assertThatExceptionOfType(IllegalArgumentException.class) diff --git a/web/src/test/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilterTests.java b/web/src/test/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilterTests.java index 38a108844c6..da25ab5f72f 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilterTests.java @@ -18,7 +18,9 @@ import java.util.Arrays; +import jakarta.servlet.FilterChain; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -27,12 +29,14 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.webauthn.api.AuthenticatorTransport; import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; @@ -47,6 +51,8 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -68,11 +74,38 @@ class PublicKeyCredentialCreationOptionsFilterTests { @Mock private WebAuthnRelyingPartyOperations rpOperations; + private PublicKeyCredentialCreationOptionsFilter filter; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + @BeforeEach + void setup() { + this.filter = new PublicKeyCredentialCreationOptionsFilter(this.rpOperations); + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + } + @AfterEach void clear() { SecurityContextHolder.clearContext(); } + @Test + void doFilterWhenCustomRequestMatcherThenUses() throws Exception { + RequestMatcher requestMatcher = mock(RequestMatcher.class); + this.filter.setRequestMatcher(requestMatcher); + FilterChain mock = mock(FilterChain.class); + this.filter.doFilter(this.request, this.response, mock); + verify(requestMatcher).matches(any()); + } + + @Test + void setRequestMatcherWhenNullThenIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequestMatcher(null)); + } + @Test void constructorWhenRpOperationsIsNullThenIllegalArgumentException() { assertThatIllegalArgumentException().isThrownBy(() -> new PublicKeyCredentialCreationOptionsFilter(null)) diff --git a/web/src/test/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilterTests.java b/web/src/test/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilterTests.java index ff0da1db9c2..9962e232893 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilterTests.java @@ -30,6 +30,7 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; import org.springframework.security.web.webauthn.api.TestCredentialRecord; @@ -100,9 +101,42 @@ class WebAuthnRegistrationFilterTests { private WebAuthnRegistrationFilter filter; + private MockHttpServletRequest request; + @BeforeEach void setup() { this.filter = new WebAuthnRegistrationFilter(this.userCredentials, this.operations); + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + this.chain = mock(FilterChain.class); + } + + @Test + void doFilterWhenCustomRequestRegisterCredentialMatcherThenUses() throws Exception { + RequestMatcher requestMatcher = mock(RequestMatcher.class); + this.filter.setRegisterCredentialMatcher(requestMatcher); + FilterChain mock = mock(FilterChain.class); + this.filter.doFilter(this.request, this.response, mock); + verify(requestMatcher).matches(any()); + } + + @Test + void doFilterWhenCustomRequestRemoveCredentialMatcherThenUses() throws Exception { + RequestMatcher requestMatcher = mock(RequestMatcher.class); + this.filter.setRemoveCredentialMatcher(requestMatcher); + FilterChain mock = mock(FilterChain.class); + this.filter.doFilter(this.request, this.response, mock); + verify(requestMatcher).matches(any()); + } + + @Test + void setRequestRegisterCredentialWhenNullThenIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRegisterCredentialMatcher(null)); + } + + @Test + void setRequestRemoveCredentialWhenNullThenIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRemoveCredentialMatcher(null)); } @Test