8 changed files with 968 additions and 0 deletions
@ -0,0 +1,196 @@
@@ -0,0 +1,196 @@
|
||||
/* |
||||
* 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.authorization; |
||||
|
||||
import java.time.Clock; |
||||
import java.time.Instant; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Objects; |
||||
import java.util.Optional; |
||||
import java.util.function.Consumer; |
||||
import java.util.function.Supplier; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.jspecify.annotations.Nullable; |
||||
|
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.authority.FactorGrantedAuthority; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* An {@link AuthorizationManager} that determines if the current user is authorized by |
||||
* evaluating if the {@link Authentication} contains a {@link FactorGrantedAuthority} that |
||||
* is not expired for each {@link RequiredFactor}. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
* @see AuthorityAuthorizationManager |
||||
*/ |
||||
public final class AllFactorsAuthorizationManager<T> implements AuthorizationManager<T> { |
||||
|
||||
private Clock clock = Clock.systemUTC(); |
||||
|
||||
private final List<RequiredFactor> requiredFactors; |
||||
|
||||
/** |
||||
* Creates a new instance. |
||||
* @param requiredFactors the authorities that are required. |
||||
*/ |
||||
private AllFactorsAuthorizationManager(List<RequiredFactor> requiredFactors) { |
||||
Assert.notEmpty(requiredFactors, "requiredFactors cannot be empty"); |
||||
Assert.noNullElements(requiredFactors, "requiredFactors must not contain null elements"); |
||||
this.requiredFactors = Collections.unmodifiableList(requiredFactors); |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link Clock} to use. |
||||
* @param clock the {@link Clock} to use. Cannot be null. |
||||
*/ |
||||
public void setClock(Clock clock) { |
||||
Assert.notNull(clock, "clock cannot be null"); |
||||
this.clock = clock; |
||||
} |
||||
|
||||
/** |
||||
* For each {@link RequiredFactor} finds the first |
||||
* {@link FactorGrantedAuthority#getAuthority()} that matches the |
||||
* {@link RequiredFactor#getAuthority()}. The |
||||
* {@link FactorGrantedAuthority#getIssuedAt()} must be more recent than |
||||
* {@link RequiredFactor#getValidDuration()} (if non-null). |
||||
* @param authentication the {@link Supplier} of the {@link Authentication} to check |
||||
* @param object the object to check authorization on (not used). |
||||
* @return an {@link FactorAuthorizationDecision} |
||||
*/ |
||||
@Override |
||||
public FactorAuthorizationDecision authorize(Supplier<? extends @Nullable Authentication> authentication, |
||||
T object) { |
||||
List<FactorGrantedAuthority> currentFactorAuthorities = getFactorGrantedAuthorities(authentication.get()); |
||||
List<RequiredFactorError> factorErrors = this.requiredFactors.stream() |
||||
.map((factor) -> requiredFactorError(factor, currentFactorAuthorities)) |
||||
.filter(Objects::nonNull) |
||||
.toList(); |
||||
return new FactorAuthorizationDecision(factorErrors); |
||||
} |
||||
|
||||
/** |
||||
* Given the {@link RequiredFactor} and the current {@link FactorGrantedAuthority} |
||||
* instances, returns {@link RequiredFactor} or null if granted. |
||||
* @param requiredFactor the {@link RequiredFactor} to check. |
||||
* @param currentFactors the current user's {@link FactorGrantedAuthority}. |
||||
* @return the {@link RequiredFactor} or null if granted. |
||||
*/ |
||||
private @Nullable RequiredFactorError requiredFactorError(RequiredFactor requiredFactor, |
||||
List<FactorGrantedAuthority> currentFactors) { |
||||
Optional<FactorGrantedAuthority> matchingAuthority = currentFactors.stream() |
||||
.filter((authority) -> authority.getAuthority().equals(requiredFactor.getAuthority())) |
||||
.findFirst(); |
||||
if (!matchingAuthority.isPresent()) { |
||||
return RequiredFactorError.createMissing(requiredFactor); |
||||
} |
||||
return matchingAuthority.map((authority) -> { |
||||
if (requiredFactor.getValidDuration() == null) { |
||||
// granted (only requires authority to match)
|
||||
return null; |
||||
} |
||||
Instant now = this.clock.instant(); |
||||
Instant expiresAt = authority.getIssuedAt().plus(requiredFactor.getValidDuration()); |
||||
if (now.isBefore(expiresAt)) { |
||||
// granted
|
||||
return null; |
||||
} |
||||
// denied (expired)
|
||||
return RequiredFactorError.createExpired(requiredFactor); |
||||
}).orElse(null); |
||||
} |
||||
|
||||
/** |
||||
* Extracts all of the {@link FactorGrantedAuthority} instances from |
||||
* {@link Authentication#getAuthorities()}. If {@link Authentication} is null, or |
||||
* {@link Authentication#isAuthenticated()} is false, then an empty {@link List} is |
||||
* returned. |
||||
* @param authentication the {@link Authentication} (possibly null). |
||||
* @return all of the {@link FactorGrantedAuthority} instances from |
||||
* {@link Authentication#getAuthorities()}. |
||||
*/ |
||||
private List<FactorGrantedAuthority> getFactorGrantedAuthorities(@Nullable Authentication authentication) { |
||||
if (authentication == null || !authentication.isAuthenticated()) { |
||||
return Collections.emptyList(); |
||||
} |
||||
// @formatter:off
|
||||
return authentication.getAuthorities().stream() |
||||
.filter(FactorGrantedAuthority.class::isInstance) |
||||
.map(FactorGrantedAuthority.class::cast) |
||||
.collect(Collectors.toList()); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
/** |
||||
* Creates a new {@link Builder} |
||||
* @return |
||||
*/ |
||||
public static Builder builder() { |
||||
return new Builder(); |
||||
} |
||||
|
||||
/** |
||||
* A builder for {@link AllFactorsAuthorizationManager}. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
*/ |
||||
public static final class Builder { |
||||
|
||||
private List<RequiredFactor> requiredFactors = new ArrayList<>(); |
||||
|
||||
/** |
||||
* Allows the user to consume the {@link RequiredFactor.Builder} that is passed in |
||||
* and then adds the result to the {@link #requiredFactor(RequiredFactor)}. |
||||
* @param requiredFactor the {@link Consumer} to invoke. |
||||
* @return the builder. |
||||
*/ |
||||
public Builder requiredFactor(Consumer<RequiredFactor.Builder> requiredFactor) { |
||||
Assert.notNull(requiredFactor, "requiredFactor cannot be null"); |
||||
RequiredFactor.Builder builder = RequiredFactor.builder(); |
||||
requiredFactor.accept(builder); |
||||
return requiredFactor(builder.build()); |
||||
} |
||||
|
||||
/** |
||||
* The {@link RequiredFactor} to add. |
||||
* @param requiredFactor the requiredFactor to add. Cannot be null. |
||||
* @return the builder. |
||||
*/ |
||||
public Builder requiredFactor(RequiredFactor requiredFactor) { |
||||
Assert.notNull(requiredFactor, "requiredFactor cannot be null"); |
||||
this.requiredFactors.add(requiredFactor); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Builds the {@link AllFactorsAuthorizationManager}. |
||||
* @param <T> the type. |
||||
* @return the {@link AllFactorsAuthorizationManager} |
||||
*/ |
||||
public <T> AllFactorsAuthorizationManager<T> build() { |
||||
return new AllFactorsAuthorizationManager<T>(this.requiredFactors); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
/* |
||||
* 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.authorization; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* An {@link AuthorizationResult} that contains {@link RequiredFactorError}. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
*/ |
||||
public class FactorAuthorizationDecision implements AuthorizationResult { |
||||
|
||||
private final List<RequiredFactorError> factorErrors; |
||||
|
||||
/** |
||||
* Creates a new instance. |
||||
* @param factorErrors the {@link RequiredFactorError}. If empty, {@link #isGranted()} |
||||
* returns true. Cannot be null or contain empty values. |
||||
*/ |
||||
public FactorAuthorizationDecision(List<RequiredFactorError> factorErrors) { |
||||
Assert.notNull(factorErrors, "factorErrors cannot be null"); |
||||
Assert.noNullElements(factorErrors, "factorErrors must not contain null elements"); |
||||
this.factorErrors = Collections.unmodifiableList(factorErrors); |
||||
} |
||||
|
||||
/** |
||||
* The specified {@link RequiredFactorError}s |
||||
* @return the errors. Cannot be null or contain null values. |
||||
*/ |
||||
public List<RequiredFactorError> getFactorErrors() { |
||||
return this.factorErrors; |
||||
} |
||||
|
||||
/** |
||||
* Returns {@code getFactorErrors().isEmpty()}. |
||||
* @return |
||||
*/ |
||||
@Override |
||||
public boolean isGranted() { |
||||
return this.factorErrors.isEmpty(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,142 @@
@@ -0,0 +1,142 @@
|
||||
/* |
||||
* 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.authorization; |
||||
|
||||
import java.time.Duration; |
||||
import java.util.Objects; |
||||
|
||||
import org.jspecify.annotations.Nullable; |
||||
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* The requirements for an {@link FactorGrantedAuthority} to be considered valid. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
*/ |
||||
public final class RequiredFactor { |
||||
|
||||
private final String authority; |
||||
|
||||
private final @Nullable Duration validDuration; |
||||
|
||||
private RequiredFactor(String authority, @Nullable Duration validDuration) { |
||||
Assert.notNull(authority, "authority cannot be null"); |
||||
this.authority = authority; |
||||
this.validDuration = validDuration; |
||||
} |
||||
|
||||
/** |
||||
* The {@link FactorGrantedAuthority#getAuthority()}. |
||||
* @return the authority. |
||||
*/ |
||||
public String getAuthority() { |
||||
return this.authority; |
||||
} |
||||
|
||||
/** |
||||
* How long the |
||||
* {@link org.springframework.security.core.authority.FactorGrantedAuthority} is valid |
||||
* for. |
||||
* @return |
||||
*/ |
||||
public @Nullable Duration getValidDuration() { |
||||
return this.validDuration; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (!(o instanceof RequiredFactor that)) { |
||||
return false; |
||||
} |
||||
return Objects.equals(this.authority, that.authority) && Objects.equals(this.validDuration, that.validDuration); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash(this.authority, this.validDuration); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "RequiredFactor [authority=" + this.authority + ", validDuration=" + this.validDuration + "]"; |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link Builder} with the specified authority. |
||||
* @param authority the authority. |
||||
* @return the builder. |
||||
*/ |
||||
public static Builder withAuthority(String authority) { |
||||
return builder().authority(authority); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new {@link Builder}. |
||||
* @return |
||||
*/ |
||||
public static Builder builder() { |
||||
return new Builder(); |
||||
} |
||||
|
||||
/** |
||||
* A builder for {@link RequiredFactor}. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
*/ |
||||
public static class Builder { |
||||
|
||||
private @Nullable String authority; |
||||
|
||||
private @Nullable Duration validDuration; |
||||
|
||||
/** |
||||
* Sets the required authority. |
||||
* @param authority the authority. |
||||
* @return the builder. |
||||
*/ |
||||
public Builder authority(String authority) { |
||||
this.authority = authority; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Sets the optional {@link Duration} of time that the {@link RequiredFactor} is |
||||
* valid for. |
||||
* @param validDuration the {@link Duration}. |
||||
* @return |
||||
*/ |
||||
public Builder validDuration(Duration validDuration) { |
||||
this.validDuration = validDuration; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Builds a new instance. |
||||
* @return |
||||
*/ |
||||
public RequiredFactor build() { |
||||
Assert.notNull(this.authority, "authority cannot be null"); |
||||
return new RequiredFactor(this.authority, this.validDuration); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,118 @@
@@ -0,0 +1,118 @@
|
||||
/* |
||||
* 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.authorization; |
||||
|
||||
import java.util.Objects; |
||||
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* An error when the requirements of {@link RequiredFactor} are not met. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
*/ |
||||
public class RequiredFactorError { |
||||
|
||||
private final RequiredFactor requiredFactor; |
||||
|
||||
private final Reason reason; |
||||
|
||||
RequiredFactorError(RequiredFactor requiredFactor, Reason reason) { |
||||
Assert.notNull(requiredFactor, "RequiredFactor must not be null"); |
||||
Assert.notNull(reason, "Reason must not be null"); |
||||
if (reason == Reason.EXPIRED && requiredFactor.getValidDuration() == null) { |
||||
throw new IllegalArgumentException( |
||||
"If expired, RequiredFactor.getValidDuration() must not be null. Got " + requiredFactor); |
||||
} |
||||
this.requiredFactor = requiredFactor; |
||||
this.reason = reason; |
||||
} |
||||
|
||||
public RequiredFactor getRequiredFactor() { |
||||
return this.requiredFactor; |
||||
} |
||||
|
||||
/** |
||||
* True if not {@link #isMissing()} but was older than the |
||||
* {@link RequiredFactor#getValidDuration()}. |
||||
* @return true if expired, else false |
||||
*/ |
||||
public boolean isExpired() { |
||||
return this.reason == Reason.EXPIRED; |
||||
} |
||||
|
||||
/** |
||||
* True if no {@link FactorGrantedAuthority#getAuthority()} on the |
||||
* {@link org.springframework.security.core.Authentication} matched |
||||
* {@link RequiredFactor#getAuthority()}. |
||||
* @return true if missing, else false. |
||||
*/ |
||||
public boolean isMissing() { |
||||
return this.reason == Reason.MISSING; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (o == null || getClass() != o.getClass()) { |
||||
return false; |
||||
} |
||||
RequiredFactorError that = (RequiredFactorError) o; |
||||
return Objects.equals(this.requiredFactor, that.requiredFactor) && this.reason == that.reason; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash(this.requiredFactor, this.reason); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "RequiredFactorError{" + "requiredFactor=" + this.requiredFactor + ", reason=" + this.reason + '}'; |
||||
} |
||||
|
||||
public static RequiredFactorError createMissing(RequiredFactor requiredFactor) { |
||||
return new RequiredFactorError(requiredFactor, Reason.MISSING); |
||||
} |
||||
|
||||
public static RequiredFactorError createExpired(RequiredFactor requiredFactor) { |
||||
return new RequiredFactorError(requiredFactor, Reason.EXPIRED); |
||||
} |
||||
|
||||
/** |
||||
* The reason that the error occurred. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
*/ |
||||
private enum Reason { |
||||
|
||||
/** |
||||
* The authority was missing. |
||||
* @see #isMissing() |
||||
*/ |
||||
MISSING, |
||||
/** |
||||
* The authority was considered expired. |
||||
* @see #isExpired() |
||||
*/ |
||||
EXPIRED |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,249 @@
@@ -0,0 +1,249 @@
|
||||
/* |
||||
* 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.authorization; |
||||
|
||||
import java.time.Clock; |
||||
import java.time.Duration; |
||||
import java.time.Instant; |
||||
import java.time.ZoneId; |
||||
import java.util.function.Consumer; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.GrantedAuthorities; |
||||
import org.springframework.security.core.authority.FactorGrantedAuthority; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Test {@link AllFactorsAuthorizationManager}. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
*/ |
||||
class AllFactorsAuthorizationManagerTests { |
||||
|
||||
private static final Object DOES_NOT_MATTER = new Object(); |
||||
|
||||
private static RequiredFactor REQUIRED_PASSWORD = RequiredFactor |
||||
.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.build(); |
||||
|
||||
private static RequiredFactor EXPIRING_PASSWORD = RequiredFactor |
||||
.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.validDuration(Duration.ofHours(1)) |
||||
.build(); |
||||
|
||||
@Test |
||||
void authorizeWhenGranted() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(REQUIRED_PASSWORD) |
||||
.build(); |
||||
FactorGrantedAuthority passwordFactor = FactorGrantedAuthority.withAuthority(REQUIRED_PASSWORD.getAuthority()) |
||||
.issuedAt(Instant.now()) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", passwordFactor); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenConsumerGranted() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor((required) -> required.authority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY)) |
||||
.build(); |
||||
FactorGrantedAuthority passwordFactor = FactorGrantedAuthority |
||||
.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.issuedAt(Instant.now()) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", passwordFactor); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenUnauthenticated() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(REQUIRED_PASSWORD) |
||||
.build(); |
||||
FactorGrantedAuthority passwordFactor = FactorGrantedAuthority.withAuthority(REQUIRED_PASSWORD.getAuthority()) |
||||
.issuedAt(Instant.now()) |
||||
.build(); |
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password", passwordFactor); |
||||
authentication.setAuthenticated(false); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isFalse(); |
||||
assertThat(result.getFactorErrors()).containsExactly(RequiredFactorError.createMissing(REQUIRED_PASSWORD)); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenNullAuthentication() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(EXPIRING_PASSWORD) |
||||
.build(); |
||||
Authentication authentication = null; |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isFalse(); |
||||
assertThat(result.getFactorErrors()).containsExactly(RequiredFactorError.createMissing(EXPIRING_PASSWORD)); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenRequiredFactorHasNullDurationThenNullIssuedAtGranted() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(REQUIRED_PASSWORD) |
||||
.build(); |
||||
FactorGrantedAuthority passwordFactor = FactorGrantedAuthority.withAuthority(REQUIRED_PASSWORD.getAuthority()) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", passwordFactor); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenRequiredFactorHasDurationAndNotFactorGrantedAuthorityThenMissing() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(EXPIRING_PASSWORD) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", |
||||
EXPIRING_PASSWORD.getAuthority()); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isFalse(); |
||||
assertThat(result.getFactorErrors()).containsExactly(RequiredFactorError.createMissing(EXPIRING_PASSWORD)); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenFactorAuthorityMissingThenMissing() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(REQUIRED_PASSWORD) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER"); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isFalse(); |
||||
assertThat(result.getFactorErrors()).containsExactly(RequiredFactorError.createMissing(REQUIRED_PASSWORD)); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenFactorGrantedAuthorityMissingThenMissing() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(REQUIRED_PASSWORD) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", |
||||
REQUIRED_PASSWORD.getAuthority()); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isFalse(); |
||||
assertThat(result.getFactorErrors()).containsExactly(RequiredFactorError.createMissing(REQUIRED_PASSWORD)); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenExpired() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(EXPIRING_PASSWORD) |
||||
.build(); |
||||
FactorGrantedAuthority passwordFactor = FactorGrantedAuthority.withAuthority(EXPIRING_PASSWORD.getAuthority()) |
||||
.issuedAt(Instant.now().minus(Duration.ofHours(2))) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", passwordFactor); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isFalse(); |
||||
assertThat(result.getFactorErrors()).containsExactly(RequiredFactorError.createExpired(EXPIRING_PASSWORD)); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenJustExpired() { |
||||
Instant now = Instant.now(); |
||||
Duration expiresIn = Duration.ofHours(1); |
||||
Instant justExpired = now.minus(expiresIn); |
||||
Clock clock = Clock.fixed(now, ZoneId.systemDefault()); |
||||
RequiredFactor expiringPassword = RequiredFactor.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.validDuration(expiresIn) |
||||
.build(); |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(expiringPassword) |
||||
.build(); |
||||
allFactors.setClock(clock); |
||||
FactorGrantedAuthority passwordFactor = FactorGrantedAuthority.withAuthority(expiringPassword.getAuthority()) |
||||
.issuedAt(justExpired) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", passwordFactor); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isFalse(); |
||||
assertThat(result.getFactorErrors()).containsExactly(RequiredFactorError.createExpired(expiringPassword)); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenAlmostExpired() { |
||||
Instant now = Instant.now(); |
||||
Duration expiresIn = Duration.ofHours(1); |
||||
Instant justExpired = now.minus(expiresIn).plus(Duration.ofNanos(1)); |
||||
Clock clock = Clock.fixed(now, ZoneId.systemDefault()); |
||||
RequiredFactor expiringPassword = RequiredFactor.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.validDuration(expiresIn) |
||||
.build(); |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(expiringPassword) |
||||
.build(); |
||||
allFactors.setClock(clock); |
||||
FactorGrantedAuthority passwordFactor = FactorGrantedAuthority.withAuthority(expiringPassword.getAuthority()) |
||||
.issuedAt(justExpired) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", passwordFactor); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void authorizeWhenDifferentFactorGrantedAuthorityThenMissing() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(REQUIRED_PASSWORD) |
||||
.build(); |
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", |
||||
FactorGrantedAuthority.fromAuthority(REQUIRED_PASSWORD.getAuthority()) + "DIFFERENT"); |
||||
FactorAuthorizationDecision result = allFactors.authorize(() -> authentication, DOES_NOT_MATTER); |
||||
assertThat(result.isGranted()).isFalse(); |
||||
assertThat(result.getFactorErrors()).containsExactly(RequiredFactorError.createMissing(REQUIRED_PASSWORD)); |
||||
} |
||||
|
||||
@Test |
||||
void setClockWhenNullThenIllegalArgumentException() { |
||||
AllFactorsAuthorizationManager<Object> allFactors = AllFactorsAuthorizationManager.builder() |
||||
.requiredFactor(REQUIRED_PASSWORD) |
||||
.build(); |
||||
assertThatIllegalArgumentException().isThrownBy(() -> allFactors.setClock(null)); |
||||
} |
||||
|
||||
@Test |
||||
void builderBuildWhenEmpty() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> AllFactorsAuthorizationManager.builder().build()); |
||||
} |
||||
|
||||
@Test |
||||
void builderWhenNullRequiredFactor() { |
||||
AllFactorsAuthorizationManager.Builder builder = AllFactorsAuthorizationManager.builder(); |
||||
assertThatIllegalArgumentException().isThrownBy(() -> builder.requiredFactor((RequiredFactor) null)); |
||||
} |
||||
|
||||
@Test |
||||
void builderWhenNullConsumerRequiredFactorBuilder() { |
||||
AllFactorsAuthorizationManager.Builder builder = AllFactorsAuthorizationManager.builder(); |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> builder.requiredFactor((Consumer<RequiredFactor.Builder>) null)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
/* |
||||
* 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.authorization; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.security.core.GrantedAuthorities; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link FactorAuthorizationDecision}. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
*/ |
||||
class FactorAuthorizationDecisionTests { |
||||
|
||||
@Test |
||||
void isGrantedWhenEmptyThenTrue() { |
||||
FactorAuthorizationDecision decision = new FactorAuthorizationDecision(List.of()); |
||||
assertThat(decision.isGranted()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void isGrantedWhenNotEmptyThenFalse() { |
||||
RequiredFactor requiredPassword = RequiredFactor.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.build(); |
||||
RequiredFactorError missingPassword = RequiredFactorError.createMissing(requiredPassword); |
||||
FactorAuthorizationDecision decision = new FactorAuthorizationDecision(List.of(missingPassword)); |
||||
assertThat(decision.isGranted()).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void getFactorErrors() { |
||||
RequiredFactor requiredPassword = RequiredFactor.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.build(); |
||||
RequiredFactorError missingPassword = RequiredFactorError.createMissing(requiredPassword); |
||||
List<RequiredFactorError> factorErrors = List.of(missingPassword); |
||||
FactorAuthorizationDecision decision = new FactorAuthorizationDecision(factorErrors); |
||||
assertThat(decision.getFactorErrors()).isEqualTo(factorErrors); |
||||
} |
||||
|
||||
@Test |
||||
void constructorWhenNullThenThrowIllegalArgumentException() { |
||||
List<RequiredFactorError> factorErrors = null; |
||||
assertThatIllegalArgumentException().isThrownBy(() -> new FactorAuthorizationDecision(factorErrors)); |
||||
} |
||||
|
||||
@Test |
||||
void constructorWhenContainsNullThenThrowIllegalArgumentException() { |
||||
RequiredFactor requiredPassword = RequiredFactor.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.build(); |
||||
RequiredFactorError missingPassword = RequiredFactorError.createMissing(requiredPassword); |
||||
List<RequiredFactorError> hasNullValue = Arrays.asList(missingPassword, null); |
||||
assertThatIllegalArgumentException().isThrownBy(() -> new FactorAuthorizationDecision(hasNullValue)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
/* |
||||
* 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.authorization; |
||||
|
||||
import java.time.Duration; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.security.core.GrantedAuthorities; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link RequiredFactorError}. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
*/ |
||||
class RequiredFactorErrorTests { |
||||
|
||||
public static final RequiredFactor REQUIRED_FACTOR = RequiredFactor |
||||
.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.validDuration(Duration.ofHours(1)) |
||||
.build(); |
||||
|
||||
@Test |
||||
void createMissing() { |
||||
RequiredFactorError error = RequiredFactorError.createMissing(REQUIRED_FACTOR); |
||||
assertThat(error.isMissing()).isTrue(); |
||||
assertThat(error.isExpired()).isFalse(); |
||||
assertThat(error.getRequiredFactor()).isEqualTo(REQUIRED_FACTOR); |
||||
} |
||||
|
||||
@Test |
||||
void createExpired() { |
||||
RequiredFactorError error = RequiredFactorError.createExpired(REQUIRED_FACTOR); |
||||
assertThat(error.isMissing()).isFalse(); |
||||
assertThat(error.isExpired()).isTrue(); |
||||
assertThat(error.getRequiredFactor()).isEqualTo(REQUIRED_FACTOR); |
||||
} |
||||
|
||||
@Test |
||||
void createExpiredWhenNullValidDurationThenIllegalArgumentException() { |
||||
RequiredFactor requiredPassword = RequiredFactor.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.build(); |
||||
assertThatIllegalArgumentException().isThrownBy(() -> RequiredFactorError.createExpired(requiredPassword)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
/* |
||||
* 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.authorization; |
||||
|
||||
import java.time.Duration; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.security.core.GrantedAuthorities; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link RequiredFactor}. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 7.0 |
||||
*/ |
||||
class RequiredFactorTests { |
||||
|
||||
@Test |
||||
void builderWhenNullAuthorityIllegalArgumentException() { |
||||
RequiredFactor.Builder builder = RequiredFactor.builder(); |
||||
assertThatIllegalArgumentException().isThrownBy(() -> builder.build()); |
||||
} |
||||
|
||||
@Test |
||||
void withAuthorityThenEquals() { |
||||
RequiredFactor requiredPassword = RequiredFactor.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.build(); |
||||
assertThat(requiredPassword.getAuthority()).isEqualTo(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY); |
||||
assertThat(requiredPassword.getValidDuration()).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void builderValidDurationThenEquals() { |
||||
Duration validDuration = Duration.ofMinutes(1); |
||||
RequiredFactor requiredPassword = RequiredFactor.withAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) |
||||
.validDuration(validDuration) |
||||
.build(); |
||||
assertThat(requiredPassword.getAuthority()).isEqualTo(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY); |
||||
assertThat(requiredPassword.getValidDuration()).isEqualTo(validDuration); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue