|  | @@ -17,6 +17,8 @@
 | 
	
		
			
				|  |  |  package org.springframework.security.web.access;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import java.util.Collection;
 | 
	
		
			
				|  |  | +import java.util.List;
 | 
	
		
			
				|  |  | +import java.util.stream.Stream;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import org.junit.jupiter.api.BeforeEach;
 | 
	
		
			
				|  |  |  import org.junit.jupiter.api.Test;
 | 
	
	
		
			
				|  | @@ -29,13 +31,18 @@ import org.springframework.mock.web.MockHttpServletResponse;
 | 
	
		
			
				|  |  |  import org.springframework.security.access.AccessDeniedException;
 | 
	
		
			
				|  |  |  import org.springframework.security.authorization.AuthorityAuthorizationDecision;
 | 
	
		
			
				|  |  |  import org.springframework.security.authorization.AuthorizationDeniedException;
 | 
	
		
			
				|  |  | +import org.springframework.security.authorization.FactorAuthorizationDecision;
 | 
	
		
			
				|  |  | +import org.springframework.security.authorization.RequiredFactor;
 | 
	
		
			
				|  |  | +import org.springframework.security.authorization.RequiredFactorError;
 | 
	
		
			
				|  |  |  import org.springframework.security.core.GrantedAuthority;
 | 
	
		
			
				|  |  |  import org.springframework.security.core.authority.AuthorityUtils;
 | 
	
		
			
				|  |  |  import org.springframework.security.web.AuthenticationEntryPoint;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.WebAttributes;
 | 
	
		
			
				|  |  |  import org.springframework.security.web.savedrequest.RequestCache;
 | 
	
		
			
				|  |  |  import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
 | 
	
		
			
				|  |  |  import org.springframework.security.web.util.matcher.RequestMatcher;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +import static org.assertj.core.api.Assertions.assertThat;
 | 
	
		
			
				|  |  |  import static org.mockito.ArgumentMatchers.any;
 | 
	
		
			
				|  |  |  import static org.mockito.Mockito.mock;
 | 
	
		
			
				|  |  |  import static org.mockito.Mockito.verify;
 | 
	
	
		
			
				|  | @@ -136,10 +143,29 @@ class DelegatingMissingAuthorityAccessDeniedHandlerTests {
 | 
	
		
			
				|  |  |  		verify(basicPasswordEntryPoint).commence(any(), any(), any());
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	// gh-18000
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void whenMultipleFactorErrorsThenPropagatesAll() throws Exception {
 | 
	
		
			
				|  |  | +		AccessDeniedHandler accessDeniedHandler = this.builder.build();
 | 
	
		
			
				|  |  | +		accessDeniedHandler.handle(this.request, this.response, missingFactors("FACTOR", "PASSWORD"));
 | 
	
		
			
				|  |  | +		verify(this.factorEntryPoint).commence(any(), any(), any());
 | 
	
		
			
				|  |  | +		Object attribute = this.request.getAttribute(WebAttributes.REQUIRED_FACTOR_ERRORS);
 | 
	
		
			
				|  |  | +		assertThat(attribute).isInstanceOf(Collection.class);
 | 
	
		
			
				|  |  | +		assertThat((Collection<?>) attribute).hasSize(2);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	AuthorizationDeniedException missingAuthorities(String... authorities) {
 | 
	
		
			
				|  |  |  		Collection<GrantedAuthority> granted = AuthorityUtils.createAuthorityList(authorities);
 | 
	
		
			
				|  |  |  		AuthorityAuthorizationDecision decision = new AuthorityAuthorizationDecision(false, granted);
 | 
	
		
			
				|  |  |  		return new AuthorizationDeniedException("access denied", decision);
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	AuthorizationDeniedException missingFactors(String... factors) {
 | 
	
		
			
				|  |  | +		List<RequiredFactorError> errors = Stream.of(factors)
 | 
	
		
			
				|  |  | +			.map((f) -> RequiredFactorError.createMissing(RequiredFactor.withAuthority(f).build()))
 | 
	
		
			
				|  |  | +			.toList();
 | 
	
		
			
				|  |  | +		FactorAuthorizationDecision decision = new FactorAuthorizationDecision(errors);
 | 
	
		
			
				|  |  | +		return new AuthorizationDeniedException("access denied", decision);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  }
 |