1
0
mirror of synced 2026-08-05 09:47:05 +00:00

Add AllRequiredFactorsAuthorizationManager.anyOf

Closes gh-18960

Signed-off-by: Evgeniy Cheban <mister.cheban@gmail.com>
This commit is contained in:
Evgeniy Cheban
2026-03-27 03:36:09 +02:00
committed by Robert Winch
parent 12997b6ab6
commit 6b09352a93
2 changed files with 89 additions and 2 deletions
@@ -20,7 +20,9 @@ import java.time.Clock;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
@@ -40,6 +42,7 @@ import org.springframework.util.Assert;
* is not expired for each {@link RequiredFactor}.
*
* @author Rob Winch
* @author Evgeniy Cheban
* @since 7.0
* @see AuthorityAuthorizationManager
*/
@@ -49,6 +52,32 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
private final List<RequiredFactor> requiredFactors;
/**
* Creates an {@link AuthorizationManager} that grants access if at least one
* {@link AllRequiredFactorsAuthorizationManager} granted, collects
* {@link RequiredFactorError}s omitting duplicate errors of the same factor.
* @param <T> the type of object that is being authorized
* @param managers the {@link AllRequiredFactorsAuthorizationManager}s to use
* @return the {@link AuthorizationManager} to use
* @since 7.1
* @see AuthorizationManagers#anyOf(AuthorizationManager[])
*/
@SafeVarargs
public static <T> AuthorizationManager<T> anyOf(AllRequiredFactorsAuthorizationManager<T>... managers) {
return (authentication, object) -> {
Map<String, RequiredFactorError> factorErrors = new LinkedHashMap<>();
for (AllRequiredFactorsAuthorizationManager<T> manager : managers) {
FactorAuthorizationDecision decision = manager.authorize(authentication, object);
if (decision.isGranted()) {
return decision;
}
decision.getFactorErrors()
.forEach((e) -> factorErrors.putIfAbsent(e.getRequiredFactor().getAuthority(), e));
}
return new FactorAuthorizationDecision(List.copyOf(factorErrors.values()));
};
}
/**
* Creates a new instance.
* @param requiredFactors the authorities that are required.
@@ -31,26 +31,37 @@ import org.springframework.security.core.authority.FactorGrantedAuthority;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
/**
* Test {@link AllRequiredFactorsAuthorizationManager}.
*
* @author Rob Winch
* @author Evgeniy Cheban
* @since 7.0
*/
class AllRequiredFactorsAuthorizationManagerTests {
private static final Object DOES_NOT_MATTER = new Object();
private static RequiredFactor REQUIRED_PASSWORD = RequiredFactor
private static final RequiredFactor REQUIRED_PASSWORD = RequiredFactor
.withAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY)
.build();
private static RequiredFactor EXPIRING_PASSWORD = RequiredFactor
private static final RequiredFactor EXPIRING_PASSWORD = RequiredFactor
.withAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY)
.validDuration(Duration.ofHours(1))
.build();
private static final RequiredFactor REQUIRED_OTT = RequiredFactor
.withAuthority(FactorGrantedAuthority.OTT_AUTHORITY)
.build();
private static final RequiredFactor EXPIRING_OTT = RequiredFactor
.withAuthority(FactorGrantedAuthority.OTT_AUTHORITY)
.validDuration(Duration.ofHours(1))
.build();
@Test
void authorizeWhenGranted() {
AllRequiredFactorsAuthorizationManager<Object> allFactors = AllRequiredFactorsAuthorizationManager.builder()
@@ -219,6 +230,53 @@ class AllRequiredFactorsAuthorizationManagerTests {
assertThat(result.getFactorErrors()).containsExactly(RequiredFactorError.createMissing(REQUIRED_PASSWORD));
}
@Test
void anyOfWhenOneGrantedThenGranted() {
AllRequiredFactorsAuthorizationManager<Object> expiringPasswordAndOtt = AllRequiredFactorsAuthorizationManager
.builder()
.requireFactor(EXPIRING_PASSWORD)
.requireFactor(EXPIRING_OTT)
.build();
AllRequiredFactorsAuthorizationManager<Object> passwordAndExpiringOtt = AllRequiredFactorsAuthorizationManager
.builder()
.requireFactor(REQUIRED_PASSWORD)
.requireFactor(EXPIRING_OTT)
.build();
FactorGrantedAuthority passwordFactor = FactorGrantedAuthority.withAuthority(EXPIRING_PASSWORD.getAuthority())
.issuedAt(Instant.now().minus(Duration.ofHours(2)))
.build();
FactorGrantedAuthority ottFactor = FactorGrantedAuthority.withAuthority(EXPIRING_OTT.getAuthority()).build();
AuthorizationManager<Object> anyOf = AllRequiredFactorsAuthorizationManager.anyOf(expiringPasswordAndOtt,
passwordAndExpiringOtt);
Authentication authentication = new TestingAuthenticationToken("user", "password", passwordFactor, ottFactor);
AuthorizationResult result = anyOf.authorize(() -> authentication, DOES_NOT_MATTER);
assertThat(result).isNotNull();
assertThat(result.isGranted()).isTrue();
}
@Test
void anyOfWhenRequiredFactorMissingThenMissing() {
AllRequiredFactorsAuthorizationManager<Object> passwordAndOtt = AllRequiredFactorsAuthorizationManager.builder()
.requireFactor(REQUIRED_PASSWORD)
.requireFactor(REQUIRED_OTT)
.build();
AllRequiredFactorsAuthorizationManager<Object> passwordAndExpiringOtt = AllRequiredFactorsAuthorizationManager
.builder()
.requireFactor(REQUIRED_PASSWORD)
.requireFactor(EXPIRING_OTT)
.build();
FactorGrantedAuthority passwordFactor = FactorGrantedAuthority.withAuthority(REQUIRED_PASSWORD.getAuthority())
.build();
AuthorizationManager<Object> anyOf = AllRequiredFactorsAuthorizationManager.anyOf(passwordAndOtt,
passwordAndExpiringOtt);
Authentication authentication = new TestingAuthenticationToken("user", "password", passwordFactor);
AuthorizationResult result = anyOf.authorize(() -> authentication, DOES_NOT_MATTER);
assertThat(result).asInstanceOf(type(FactorAuthorizationDecision.class)).satisfies((decision) -> {
assertThat(decision.isGranted()).isFalse();
assertThat(decision.getFactorErrors()).containsExactly(RequiredFactorError.createMissing(REQUIRED_OTT));
});
}
@Test
void setClockWhenNullThenIllegalArgumentException() {
AllRequiredFactorsAuthorizationManager<Object> allFactors = AllRequiredFactorsAuthorizationManager.builder()