Add AllRequiredFactorsAuthorizationManager.anyOf
Closes gh-18960 Signed-off-by: Evgeniy Cheban <mister.cheban@gmail.com>
This commit is contained in:
committed by
Robert Winch
parent
12997b6ab6
commit
6b09352a93
+29
@@ -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.
|
||||
|
||||
+60
-2
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user