mirror of
https://github.com/spring-projects/spring-security.git
synced 2026-03-30 22:12:48 +00:00
Merge pull request #19004 from rwinch/CredentialRecordOwnerAuthorizationManager
Add CredentialRecordOwnerAuthorizationManager
This commit is contained in:
commit
5a4ada04ac
@ -36,6 +36,7 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity
|
||||
import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter;
|
||||
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
|
||||
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationProvider;
|
||||
import org.springframework.security.web.webauthn.management.CredentialRecordOwnerAuthorizationManager;
|
||||
import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository;
|
||||
import org.springframework.security.web.webauthn.management.MapUserCredentialRepository;
|
||||
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
|
||||
@ -166,6 +167,8 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
|
||||
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
|
||||
rpOperations);
|
||||
webAuthnRegistrationFilter.setDeleteCredentialAuthorizationManager(
|
||||
new CredentialRecordOwnerAuthorizationManager(userCredentials, userEntities));
|
||||
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
|
||||
rpOperations);
|
||||
if (creationOptionsRepository != null) {
|
||||
|
||||
@ -42,8 +42,15 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
|
||||
import org.springframework.security.web.webauthn.api.Bytes;
|
||||
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
|
||||
import org.springframework.security.web.webauthn.api.TestCredentialRecords;
|
||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
|
||||
import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository;
|
||||
import org.springframework.security.web.webauthn.management.MapUserCredentialRepository;
|
||||
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
|
||||
import org.springframework.security.web.webauthn.management.UserCredentialRepository;
|
||||
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
|
||||
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@ -56,6 +63,7 @@ import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||
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.content;
|
||||
@ -247,6 +255,24 @@ public class WebAuthnConfigurerTests {
|
||||
.andExpect(content().string(expectedBody));
|
||||
}
|
||||
|
||||
@Test
|
||||
void webauthnWhenDeleteAndCredentialBelongsToUserThenNoContent() throws Exception {
|
||||
this.spring.register(DeleteCredentialConfiguration.class).autowire();
|
||||
this.mvc
|
||||
.perform(delete("/webauthn/register/" + DeleteCredentialConfiguration.CREDENTIAL_ID_BASE64URL)
|
||||
.with(authentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"))))
|
||||
.andExpect(status().isNoContent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void webauthnWhenDeleteAndCredentialBelongsToDifferentUserThenForbidden() throws Exception {
|
||||
this.spring.register(DeleteCredentialConfiguration.class).autowire();
|
||||
this.mvc
|
||||
.perform(delete("/webauthn/register/" + DeleteCredentialConfiguration.CREDENTIAL_ID_BASE64URL)
|
||||
.with(authentication(new TestingAuthenticationToken("other-user", "password", "ROLE_USER"))))
|
||||
.andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class ConfigCredentialCreationOptionsRepository {
|
||||
@ -417,4 +443,47 @@ public class WebAuthnConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class DeleteCredentialConfiguration {
|
||||
|
||||
static final String CREDENTIAL_ID_BASE64URL = "NauGCN7bZ5jEBwThcde51g";
|
||||
|
||||
static final Bytes USER_ENTITY_ID = Bytes.fromBase64("vKBFhsWT3gQnn-gHdT4VXIvjDkVXVYg5w8CLGHPunMM");
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
|
||||
return mock(WebAuthnRelyingPartyOperations.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
UserCredentialRepository userCredentialRepository() {
|
||||
MapUserCredentialRepository repository = new MapUserCredentialRepository();
|
||||
repository.save(TestCredentialRecords.userCredential().build());
|
||||
return repository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
PublicKeyCredentialUserEntityRepository userEntityRepository() {
|
||||
MapPublicKeyCredentialUserEntityRepository repository = new MapPublicKeyCredentialUserEntityRepository();
|
||||
repository.save(ImmutablePublicKeyCredentialUserEntity.builder()
|
||||
.name("user")
|
||||
.id(USER_ENTITY_ID)
|
||||
.displayName("User")
|
||||
.build());
|
||||
return repository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.csrf(AbstractHttpConfigurer::disable).webAuthn(Customizer.withDefaults()).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.security.web.webauthn.management;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.webauthn.api.Bytes;
|
||||
import org.springframework.security.web.webauthn.api.CredentialRecord;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AuthorizationManager} that grants access when the {@link CredentialRecord}
|
||||
* identified by the provided credential id is owned by the currently authenticated user.
|
||||
*
|
||||
* <p>
|
||||
* Per the <a href="https://www.w3.org/TR/webauthn-3/#credential-id">WebAuthn
|
||||
* specification</a>, a credential id must contain at least 16 bytes with at least 100
|
||||
* bits of entropy, making it practically unguessable. The specification also advises that
|
||||
* credential ids should be kept private, as exposing them can leak personally identifying
|
||||
* information (see
|
||||
* <a href="https://www.w3.org/TR/webauthn-3/#sctn-credential-id-privacy-leak">§ 14.6.3
|
||||
* Privacy leak via credential IDs</a>). This {@link AuthorizationManager} is therefore
|
||||
* intended as defense in depth: even if a credential id were somehow exposed, an
|
||||
* unauthorized user could not delete another user's credential.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 6.5.10
|
||||
*/
|
||||
public final class CredentialRecordOwnerAuthorizationManager implements AuthorizationManager<Bytes> {
|
||||
|
||||
private final AuthenticatedAuthorizationManager<Bytes> authenticatedAuthorizationManager = AuthenticatedAuthorizationManager
|
||||
.authenticated();
|
||||
|
||||
private final UserCredentialRepository userCredentials;
|
||||
|
||||
private final PublicKeyCredentialUserEntityRepository userEntities;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param userCredentials the {@link UserCredentialRepository} to use
|
||||
* @param userEntities the {@link PublicKeyCredentialUserEntityRepository} to use
|
||||
*/
|
||||
public CredentialRecordOwnerAuthorizationManager(UserCredentialRepository userCredentials,
|
||||
PublicKeyCredentialUserEntityRepository userEntities) {
|
||||
Assert.notNull(userCredentials, "userCredentials cannot be null");
|
||||
Assert.notNull(userEntities, "userEntities cannot be null");
|
||||
this.userCredentials = userCredentials;
|
||||
this.userEntities = userEntities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, Bytes credentialId) {
|
||||
AuthorizationDecision decision = this.authenticatedAuthorizationManager.check(authentication, credentialId);
|
||||
if (!decision.isGranted()) {
|
||||
return decision;
|
||||
}
|
||||
Authentication auth = authentication.get();
|
||||
CredentialRecord credential = this.userCredentials.findByCredentialId(credentialId);
|
||||
if (credential == null) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
if (credential.getUserEntityUserId() == null) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
PublicKeyCredentialUserEntity userEntity = this.userEntities.findByUsername(auth.getName());
|
||||
if (userEntity == null) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
return new AuthorizationDecision(credential.getUserEntityUserId().equals(userEntity.getId()));
|
||||
}
|
||||
|
||||
}
|
||||
@ -17,6 +17,7 @@
|
||||
package org.springframework.security.web.webauthn.registration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import jakarta.servlet.FilterChain;
|
||||
@ -34,6 +35,12 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.authorization.SingleResultAuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.webauthn.api.Bytes;
|
||||
@ -87,6 +94,9 @@ public class WebAuthnRegistrationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final UserCredentialRepository userCredentials;
|
||||
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
|
||||
.getContextHolderStrategy();
|
||||
|
||||
private HttpMessageConverter<Object> converter = new MappingJackson2HttpMessageConverter(
|
||||
JsonMapper.builder().addModule(new WebauthnJackson2Module()).build());
|
||||
|
||||
@ -98,6 +108,9 @@ public class WebAuthnRegistrationFilter extends OncePerRequestFilter {
|
||||
private RequestMatcher removeCredentialMatcher = PathPatternRequestMatcher.withDefaults()
|
||||
.matcher(HttpMethod.DELETE, "/webauthn/register/{id}");
|
||||
|
||||
private AuthorizationManager<Bytes> deleteCredentialAuthorizationManager = SingleResultAuthorizationManager
|
||||
.denyAll();
|
||||
|
||||
public WebAuthnRegistrationFilter(UserCredentialRepository userCredentials,
|
||||
WebAuthnRelyingPartyOperations rpOptions) {
|
||||
Assert.notNull(userCredentials, "userCredentials must not be null");
|
||||
@ -132,6 +145,42 @@ public class WebAuthnRegistrationFilter extends OncePerRequestFilter {
|
||||
this.removeCredentialMatcher = removeCredentialMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthorizationManager} used to authorize the delete credential
|
||||
* operation. The object being authorized is the credential id as {@link Bytes}. By
|
||||
* default, all delete requests are denied.
|
||||
*
|
||||
* <p>
|
||||
* Per the <a href="https://www.w3.org/TR/webauthn-3/#credential-id">WebAuthn
|
||||
* specification</a>, a credential id must contain at least 16 bytes with at least 100
|
||||
* bits of entropy, making it practically unguessable. The specification also advises
|
||||
* that credential ids should be kept private, as exposing them can leak personally
|
||||
* identifying information (see
|
||||
* <a href="https://www.w3.org/TR/webauthn-3/#sctn-credential-id-privacy-leak">§
|
||||
* 14.6.3 Privacy leak via credential IDs</a>). This {@link AuthorizationManager} is
|
||||
* therefore intended as defense in depth: even if a credential id were somehow
|
||||
* exposed, an unauthorized user could not delete another user's credential.
|
||||
* @param deleteCredentialAuthorizationManager the {@link AuthorizationManager} to use
|
||||
* @since 6.5.10
|
||||
*/
|
||||
public void setDeleteCredentialAuthorizationManager(
|
||||
AuthorizationManager<Bytes> deleteCredentialAuthorizationManager) {
|
||||
Assert.notNull(deleteCredentialAuthorizationManager, "deleteCredentialAuthorizationManager cannot be null");
|
||||
this.deleteCredentialAuthorizationManager = deleteCredentialAuthorizationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link SecurityContextHolderStrategy} to use. The default is
|
||||
* {@link SecurityContextHolder#getContextHolderStrategy()}.
|
||||
* @param securityContextHolderStrategy the {@link SecurityContextHolderStrategy} to
|
||||
* use
|
||||
* @since 6.5.10
|
||||
*/
|
||||
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
|
||||
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
|
||||
this.securityContextHolderStrategy = securityContextHolderStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
@ -203,7 +252,15 @@ public class WebAuthnRegistrationFilter extends OncePerRequestFilter {
|
||||
|
||||
private void removeCredential(HttpServletRequest request, HttpServletResponse response, String id)
|
||||
throws IOException {
|
||||
this.userCredentials.delete(Bytes.fromBase64(id));
|
||||
Bytes credentialId = Bytes.fromBase64(id);
|
||||
Supplier<Authentication> authentication = () -> this.securityContextHolderStrategy.getContext()
|
||||
.getAuthentication();
|
||||
AuthorizationResult result = this.deleteCredentialAuthorizationManager.authorize(authentication, credentialId);
|
||||
if (result != null && !result.isGranted()) {
|
||||
response.setStatus(HttpStatus.FORBIDDEN.value());
|
||||
return;
|
||||
}
|
||||
this.userCredentials.delete(credentialId);
|
||||
response.setStatus(HttpStatus.NO_CONTENT.value());
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.security.web.webauthn.management;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.web.webauthn.api.Bytes;
|
||||
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
|
||||
import org.springframework.security.web.webauthn.api.TestBytes;
|
||||
import org.springframework.security.web.webauthn.api.TestCredentialRecords;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
/**
|
||||
* Tests for {@link CredentialRecordOwnerAuthorizationManager}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 6.5.10
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CredentialRecordOwnerAuthorizationManagerTests {
|
||||
|
||||
@Mock
|
||||
private UserCredentialRepository userCredentials;
|
||||
|
||||
@Mock
|
||||
private PublicKeyCredentialUserEntityRepository userEntities;
|
||||
|
||||
@Test
|
||||
void constructorWhenNullUserCredentialsThenIllegalArgument() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new CredentialRecordOwnerAuthorizationManager(null, this.userEntities));
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorWhenNullUserEntitiesTonIllegalArgument() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new CredentialRecordOwnerAuthorizationManager(this.userCredentials, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkWhenAuthenticationNullThenDenied() {
|
||||
CredentialRecordOwnerAuthorizationManager manager = new CredentialRecordOwnerAuthorizationManager(
|
||||
this.userCredentials, this.userEntities);
|
||||
Bytes credentialId = TestCredentialRecords.userCredential().build().getCredentialId();
|
||||
AuthorizationDecision decision = manager.check(() -> null, credentialId);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkWhenNotAuthenticatedThenDenied() {
|
||||
CredentialRecordOwnerAuthorizationManager manager = new CredentialRecordOwnerAuthorizationManager(
|
||||
this.userCredentials, this.userEntities);
|
||||
Bytes credentialId = TestCredentialRecords.userCredential().build().getCredentialId();
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
|
||||
authentication.setAuthenticated(false);
|
||||
AuthorizationDecision decision = manager.check(() -> authentication, credentialId);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkWhenCredentialNotFoundThenDenied() {
|
||||
CredentialRecordOwnerAuthorizationManager manager = new CredentialRecordOwnerAuthorizationManager(
|
||||
this.userCredentials, this.userEntities);
|
||||
Bytes credentialId = TestCredentialRecords.userCredential().build().getCredentialId();
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password", "USER");
|
||||
AuthorizationDecision decision = manager.check(() -> authentication, credentialId);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkWhenCredentialUserEntityUserIdNullThenDenied() {
|
||||
CredentialRecordOwnerAuthorizationManager manager = new CredentialRecordOwnerAuthorizationManager(
|
||||
this.userCredentials, this.userEntities);
|
||||
Bytes credentialId = TestCredentialRecords.userCredential().build().getCredentialId();
|
||||
given(this.userCredentials.findByCredentialId(credentialId))
|
||||
.willReturn(TestCredentialRecords.userCredential().userEntityUserId(null).build());
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password", "USER");
|
||||
AuthorizationDecision decision = manager.check(() -> authentication, credentialId);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkWhenUserEntityNotFoundThenDenied() {
|
||||
CredentialRecordOwnerAuthorizationManager manager = new CredentialRecordOwnerAuthorizationManager(
|
||||
this.userCredentials, this.userEntities);
|
||||
Bytes credentialId = TestCredentialRecords.userCredential().build().getCredentialId();
|
||||
given(this.userCredentials.findByCredentialId(credentialId))
|
||||
.willReturn(TestCredentialRecords.userCredential().build());
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password", "USER");
|
||||
AuthorizationDecision decision = manager.check(() -> authentication, credentialId);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkWhenCredentialBelongsToUserThenGranted() {
|
||||
CredentialRecordOwnerAuthorizationManager manager = new CredentialRecordOwnerAuthorizationManager(
|
||||
this.userCredentials, this.userEntities);
|
||||
Bytes credentialId = TestCredentialRecords.userCredential().build().getCredentialId();
|
||||
Bytes userId = TestCredentialRecords.userCredential().build().getUserEntityUserId();
|
||||
given(this.userCredentials.findByCredentialId(credentialId))
|
||||
.willReturn(TestCredentialRecords.userCredential().build());
|
||||
PublicKeyCredentialUserEntity userEntity = ImmutablePublicKeyCredentialUserEntity.builder()
|
||||
.name("user")
|
||||
.id(userId)
|
||||
.displayName("User")
|
||||
.build();
|
||||
given(this.userEntities.findByUsername("user")).willReturn(userEntity);
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password", "USER");
|
||||
AuthorizationDecision decision = manager.check(() -> authentication, credentialId);
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkWhenCredentialBelongsToDifferentUserThenDenied() {
|
||||
CredentialRecordOwnerAuthorizationManager manager = new CredentialRecordOwnerAuthorizationManager(
|
||||
this.userCredentials, this.userEntities);
|
||||
Bytes credentialId = TestCredentialRecords.userCredential().build().getCredentialId();
|
||||
given(this.userCredentials.findByCredentialId(credentialId))
|
||||
.willReturn(TestCredentialRecords.userCredential().build());
|
||||
PublicKeyCredentialUserEntity otherUserEntity = ImmutablePublicKeyCredentialUserEntity.builder()
|
||||
.name("user")
|
||||
.id(TestBytes.get())
|
||||
.displayName("User")
|
||||
.build();
|
||||
given(this.userEntities.findByUsername("user")).willReturn(otherUserEntity);
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password", "USER");
|
||||
AuthorizationDecision decision = manager.check(() -> authentication, credentialId);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
@ -31,7 +31,11 @@ import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.SingleResultAuthorizationManager;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.webauthn.api.Bytes;
|
||||
import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord;
|
||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
|
||||
import org.springframework.security.web.webauthn.api.TestCredentialRecords;
|
||||
@ -213,12 +217,41 @@ class WebAuthnRegistrationFilterTests {
|
||||
|
||||
@Test
|
||||
void doFilterWhenDeleteSuccessThenNoContent() throws Exception {
|
||||
this.filter.setDeleteCredentialAuthorizationManager(SingleResultAuthorizationManager.permitAll());
|
||||
MockHttpServletRequest request = MockMvcRequestBuilders.delete("/webauthn/register/123456")
|
||||
.buildRequest(new MockServletContext());
|
||||
this.filter.doFilter(request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setDeleteCredentialAuthorizationManagerWhenNullThenIllegalArgument() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.filter.setDeleteCredentialAuthorizationManager(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doFilterWhenDeleteAndCustomAuthorizationManagerThenUses() throws Exception {
|
||||
AuthorizationManager<Bytes> authorizationManager = mock(AuthorizationManager.class);
|
||||
given(authorizationManager.authorize(any(), any())).willReturn(new AuthorizationDecision(true));
|
||||
this.filter.setDeleteCredentialAuthorizationManager(authorizationManager);
|
||||
MockHttpServletRequest request = MockMvcRequestBuilders.delete("/webauthn/register/123456")
|
||||
.buildRequest(new MockServletContext());
|
||||
this.filter.doFilter(request, this.response, this.chain);
|
||||
verify(authorizationManager).authorize(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void doFilterWhenDeleteAndAuthorizationDeniedThenForbidden() throws Exception {
|
||||
AuthorizationManager<Bytes> authorizationManager = mock(AuthorizationManager.class);
|
||||
given(authorizationManager.authorize(any(), any())).willReturn(new AuthorizationDecision(false));
|
||||
this.filter.setDeleteCredentialAuthorizationManager(authorizationManager);
|
||||
MockHttpServletRequest request = MockMvcRequestBuilders.delete("/webauthn/register/123456")
|
||||
.buildRequest(new MockServletContext());
|
||||
this.filter.doFilter(request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value());
|
||||
}
|
||||
|
||||
private static MockHttpServletRequest registerCredentialRequest(String body) {
|
||||
return MockMvcRequestBuilders.post(WebAuthnRegistrationFilter.DEFAULT_REGISTER_CREDENTIAL_URL)
|
||||
.content(body)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user