parent
3f74991ce9
commit
d1ff983c11
|
@ -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 @@
|
|||
/*
|
||||
* 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 @@
|
|||
/*
|
||||
* 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 @@
|
|||
/*
|
||||
* 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 @@
|
|||
/*
|
||||
* 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 @@
|
|||
/*
|
||||
* 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 @@
|
|||
/*
|
||||
* 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 @@
|
|||
/*
|
||||
* 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