Polish AllRequiredFactorsAuthorizationManager.anyOf
- Add validation - Extract to static inner class - Uniqueness determined by Set rather than requiredFactor This is important for the failure with the same RequiredFactor, but a different reason - Add documentation Signed-off-by: Robert Winch <362503+rwinch@users.noreply.github.com>
This commit is contained in:
+44
-17
@@ -20,11 +20,11 @@ import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -54,28 +54,23 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* {@link AllRequiredFactorsAuthorizationManager} granted. When all managers deny,
|
||||
* collects the unique {@link RequiredFactorError}s from each manager.
|
||||
* @param <T> the type of object that is being authorized
|
||||
* @param managers the {@link AllRequiredFactorsAuthorizationManager}s to use
|
||||
* @param managers the {@link AllRequiredFactorsAuthorizationManager}s to use; cannot
|
||||
* be empty or contain null elements
|
||||
* @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()));
|
||||
};
|
||||
Assert.notEmpty(managers, "managers cannot be empty");
|
||||
Assert.noNullElements(managers, "managers cannot contain null elements");
|
||||
if (managers.length == 1) {
|
||||
return managers[0];
|
||||
}
|
||||
return new AnyOfFactorsAuthorizationManager<>(managers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,6 +174,38 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
|
||||
return new Builder<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link AuthorizationManager} that grants access if at least one
|
||||
* {@link AllRequiredFactorsAuthorizationManager} granted. When all deny, collects the
|
||||
* unique {@link RequiredFactorError}s from each manager.
|
||||
*
|
||||
* @param <T> the type of object being authorized
|
||||
*/
|
||||
private static final class AnyOfFactorsAuthorizationManager<T> implements AuthorizationManager<T> {
|
||||
|
||||
private final AllRequiredFactorsAuthorizationManager<T>[] managers;
|
||||
|
||||
AnyOfFactorsAuthorizationManager(AllRequiredFactorsAuthorizationManager<T>[] managers) {
|
||||
Assert.notEmpty(managers, "managers cannot be empty");
|
||||
Assert.noNullElements(managers, "managers cannot contain null elements");
|
||||
this.managers = managers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, T object) {
|
||||
Set<RequiredFactorError> factorErrors = new LinkedHashSet<>();
|
||||
for (AllRequiredFactorsAuthorizationManager<T> manager : this.managers) {
|
||||
FactorAuthorizationDecision decision = manager.authorize(authentication, object);
|
||||
if (decision.isGranted()) {
|
||||
return decision;
|
||||
}
|
||||
factorErrors.addAll(decision.getFactorErrors());
|
||||
}
|
||||
return new FactorAuthorizationDecision(List.copyOf(factorErrors));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link AllRequiredFactorsAuthorizationManager}.
|
||||
*
|
||||
|
||||
+56
-4
@@ -44,11 +44,11 @@ class AllRequiredFactorsAuthorizationManagerTests {
|
||||
|
||||
private static final Object DOES_NOT_MATTER = new Object();
|
||||
|
||||
private static final RequiredFactor REQUIRED_PASSWORD = RequiredFactor
|
||||
private static RequiredFactor REQUIRED_PASSWORD = RequiredFactor
|
||||
.withAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY)
|
||||
.build();
|
||||
|
||||
private static final RequiredFactor EXPIRING_PASSWORD = RequiredFactor
|
||||
private static RequiredFactor EXPIRING_PASSWORD = RequiredFactor
|
||||
.withAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY)
|
||||
.validDuration(Duration.ofHours(1))
|
||||
.build();
|
||||
@@ -255,7 +255,7 @@ class AllRequiredFactorsAuthorizationManagerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void anyOfWhenRequiredFactorMissingThenMissing() {
|
||||
void anyOfWhenSameAuthorityDifferentValidDurationThenBothErrorsReturned() {
|
||||
AllRequiredFactorsAuthorizationManager<Object> passwordAndOtt = AllRequiredFactorsAuthorizationManager.builder()
|
||||
.requireFactor(REQUIRED_PASSWORD)
|
||||
.requireFactor(REQUIRED_OTT)
|
||||
@@ -273,10 +273,62 @@ class AllRequiredFactorsAuthorizationManagerTests {
|
||||
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));
|
||||
assertThat(decision.getFactorErrors()).containsExactly(RequiredFactorError.createMissing(REQUIRED_OTT),
|
||||
RequiredFactorError.createMissing(EXPIRING_OTT));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void anyOfWhenIdenticalErrorInMultipleManagersThenDeduplicated() {
|
||||
AllRequiredFactorsAuthorizationManager<Object> passwordAndOtt = AllRequiredFactorsAuthorizationManager.builder()
|
||||
.requireFactor(REQUIRED_PASSWORD)
|
||||
.requireFactor(REQUIRED_OTT)
|
||||
.build();
|
||||
AllRequiredFactorsAuthorizationManager<Object> passwordOnly = AllRequiredFactorsAuthorizationManager.builder()
|
||||
.requireFactor(REQUIRED_PASSWORD)
|
||||
.build();
|
||||
AuthorizationManager<Object> anyOf = AllRequiredFactorsAuthorizationManager.anyOf(passwordAndOtt, passwordOnly);
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
AuthorizationResult result = anyOf.authorize(() -> authentication, DOES_NOT_MATTER);
|
||||
assertThat(result).asInstanceOf(type(FactorAuthorizationDecision.class)).satisfies((decision) -> {
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
assertThat(decision.getFactorErrors()).containsOnly(RequiredFactorError.createMissing(REQUIRED_PASSWORD),
|
||||
RequiredFactorError.createMissing(REQUIRED_OTT));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void anyOfWhenDeniedThenErrorsRetainedInManagerOrder() {
|
||||
AllRequiredFactorsAuthorizationManager<Object> passwordOnly = AllRequiredFactorsAuthorizationManager.builder()
|
||||
.requireFactor(REQUIRED_PASSWORD)
|
||||
.build();
|
||||
AllRequiredFactorsAuthorizationManager<Object> ottOnly = AllRequiredFactorsAuthorizationManager.builder()
|
||||
.requireFactor(REQUIRED_OTT)
|
||||
.build();
|
||||
AuthorizationManager<Object> anyOf = AllRequiredFactorsAuthorizationManager.anyOf(passwordOnly, ottOnly);
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
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_PASSWORD),
|
||||
RequiredFactorError.createMissing(REQUIRED_OTT));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void anyOfWhenEmptyManagersThenIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AllRequiredFactorsAuthorizationManager.anyOf());
|
||||
}
|
||||
|
||||
@Test
|
||||
void anyOfWhenSingleManagerThenReturnsSameInstance() {
|
||||
AllRequiredFactorsAuthorizationManager<Object> manager = AllRequiredFactorsAuthorizationManager.builder()
|
||||
.requireFactor(REQUIRED_PASSWORD)
|
||||
.build();
|
||||
AuthorizationManager<Object> result = AllRequiredFactorsAuthorizationManager.anyOf(manager);
|
||||
assertThat(result == manager).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setClockWhenNullThenIllegalArgumentException() {
|
||||
AllRequiredFactorsAuthorizationManager<Object> allFactors = AllRequiredFactorsAuthorizationManager.builder()
|
||||
|
||||
Reference in New Issue
Block a user