Revert unnecessary commits from main

Issue gh-15016
This commit is contained in:
Marcus Hert Da Coregio
2024-05-08 13:44:07 -03:00
parent e4745c99e9
commit 08f11f06ab
317 changed files with 1281 additions and 30496 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2016 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.

View File

@@ -125,7 +125,7 @@ public interface SecurityExpressionOperations {
* given the permission
* @param target the target domain object to check permission on
* @param permission the permission to check on the domain object (i.e. "read",
* "write", etc.).
* "write", etc).
* @return true if permission is granted to the {@link #getAuthentication()}, else
* false
*/
@@ -136,8 +136,8 @@ public interface SecurityExpressionOperations {
* object with a given id, type, and permission.
* @param targetId the identifier of the domain object to determine access
* @param targetType the type (i.e. com.example.domain.Message)
* @param permission the permission to check on the domain object (i.e. "read",
* "write", etc.)
* @param permission the perission to check on the domain object (i.e. "read",
* "write", etc)
* @return true if permission is granted to the {@link #getAuthentication()}, else
* false
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2019 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.
@@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@@ -31,7 +30,6 @@ import org.springframework.core.log.LogMessage;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.util.Assert;
/**
* <p>
@@ -76,69 +74,31 @@ import org.springframework.util.Assert;
* your intentions clearer.
*
* @author Michael Mayr
* @author Josh Cummings
*/
public class RoleHierarchyImpl implements RoleHierarchy {
private static final Log logger = LogFactory.getLog(RoleHierarchyImpl.class);
/**
* Raw hierarchy configuration where each line represents single or multiple level
* role chain.
*/
private String roleHierarchyStringRepresentation = null;
/**
* {@code rolesReachableInOneStepMap} is a Map that under the key of a specific role
* name contains a set of all roles reachable from this role in 1 step (i.e. parsed
* {@link #roleHierarchyStringRepresentation} grouped by the higher role)
*/
private Map<String, Set<GrantedAuthority>> rolesReachableInOneStepMap = null;
/**
* {@code rolesReachableInOneOrMoreStepsMap} is a Map that under the key of a specific
* role name contains a set of all roles reachable from this role in 1 or more steps
* (i.e. fully resolved hierarchy from {@link #rolesReachableInOneStepMap})
*/
private Map<String, Set<GrantedAuthority>> rolesReachableInOneOrMoreStepsMap = null;
/**
* @deprecated Use {@link RoleHierarchyImpl#fromHierarchy} instead
*/
@Deprecated
public RoleHierarchyImpl() {
}
private RoleHierarchyImpl(Map<String, Set<GrantedAuthority>> hierarchy) {
this.rolesReachableInOneOrMoreStepsMap = buildRolesReachableInOneOrMoreStepsMap(hierarchy);
}
/**
* Create a role hierarchy instance with the given definition, similar to the
* following:
*
* <pre>
* ROLE_A &gt; ROLE_B
* ROLE_B &gt; ROLE_AUTHENTICATED
* ROLE_AUTHENTICATED &gt; ROLE_UNAUTHENTICATED
* </pre>
* @param hierarchy the role hierarchy to use
* @return a {@link RoleHierarchyImpl} that uses the given {@code hierarchy}
*/
public static RoleHierarchyImpl fromHierarchy(String hierarchy) {
return new RoleHierarchyImpl(buildRolesReachableInOneStepMap(hierarchy));
}
/**
* Factory method that creates a {@link Builder} instance with the default role prefix
* "ROLE_"
* @return a {@link Builder} instance with the default role prefix "ROLE_"
* @since 6.3
*/
public static Builder withDefaultRolePrefix() {
return withRolePrefix("ROLE_");
}
/**
* Factory method that creates a {@link Builder} instance with the specified role
* prefix.
* @param rolePrefix the prefix to be used for the roles in the hierarchy.
* @return a new {@link Builder} instance with the specified role prefix
* @throws IllegalArgumentException if the provided role prefix is null
* @since 6.3
*/
public static Builder withRolePrefix(String rolePrefix) {
Assert.notNull(rolePrefix, "rolePrefix must not be null");
return new Builder(rolePrefix);
}
/**
* Set the role hierarchy and pre-calculate for every role the set of all reachable
* roles, i.e. all roles lower in the hierarchy of every given role. Pre-calculation
@@ -146,15 +106,13 @@ public class RoleHierarchyImpl implements RoleHierarchy {
* time). During pre-calculation, cycles in role hierarchy are detected and will cause
* a <tt>CycleInRoleHierarchyException</tt> to be thrown.
* @param roleHierarchyStringRepresentation - String definition of the role hierarchy.
* @deprecated Use {@link RoleHierarchyImpl#fromHierarchy} instead
*/
@Deprecated
public void setHierarchy(String roleHierarchyStringRepresentation) {
this.roleHierarchyStringRepresentation = roleHierarchyStringRepresentation;
logger.debug(LogMessage.format("setHierarchy() - The following role hierarchy was set: %s",
roleHierarchyStringRepresentation));
Map<String, Set<GrantedAuthority>> hierarchy = buildRolesReachableInOneStepMap(
roleHierarchyStringRepresentation);
this.rolesReachableInOneOrMoreStepsMap = buildRolesReachableInOneOrMoreStepsMap(hierarchy);
buildRolesReachableInOneStepMap();
buildRolesReachableInOneOrMoreStepsMap();
}
@Override
@@ -198,21 +156,21 @@ public class RoleHierarchyImpl implements RoleHierarchy {
* Parse input and build the map for the roles reachable in one step: the higher role
* will become a key that references a set of the reachable lower roles.
*/
private static Map<String, Set<GrantedAuthority>> buildRolesReachableInOneStepMap(String hierarchy) {
Map<String, Set<GrantedAuthority>> rolesReachableInOneStepMap = new HashMap<>();
for (String line : hierarchy.split("\n")) {
private void buildRolesReachableInOneStepMap() {
this.rolesReachableInOneStepMap = new HashMap<>();
for (String line : this.roleHierarchyStringRepresentation.split("\n")) {
// Split on > and trim excessive whitespace
String[] roles = line.trim().split("\\s+>\\s+");
for (int i = 1; i < roles.length; i++) {
String higherRole = roles[i - 1];
GrantedAuthority lowerRole = new SimpleGrantedAuthority(roles[i]);
Set<GrantedAuthority> rolesReachableInOneStepSet;
if (!rolesReachableInOneStepMap.containsKey(higherRole)) {
if (!this.rolesReachableInOneStepMap.containsKey(higherRole)) {
rolesReachableInOneStepSet = new HashSet<>();
rolesReachableInOneStepMap.put(higherRole, rolesReachableInOneStepSet);
this.rolesReachableInOneStepMap.put(higherRole, rolesReachableInOneStepSet);
}
else {
rolesReachableInOneStepSet = rolesReachableInOneStepMap.get(higherRole);
rolesReachableInOneStepSet = this.rolesReachableInOneStepMap.get(higherRole);
}
rolesReachableInOneStepSet.add(lowerRole);
logger.debug(LogMessage.format(
@@ -220,7 +178,6 @@ public class RoleHierarchyImpl implements RoleHierarchy {
higherRole, lowerRole));
}
}
return rolesReachableInOneStepMap;
}
/**
@@ -229,105 +186,30 @@ public class RoleHierarchyImpl implements RoleHierarchy {
* CycleInRoleHierarchyException if a cycle in the role hierarchy definition is
* detected)
*/
private static Map<String, Set<GrantedAuthority>> buildRolesReachableInOneOrMoreStepsMap(
Map<String, Set<GrantedAuthority>> hierarchy) {
Map<String, Set<GrantedAuthority>> rolesReachableInOneOrMoreStepsMap = new HashMap<>();
private void buildRolesReachableInOneOrMoreStepsMap() {
this.rolesReachableInOneOrMoreStepsMap = new HashMap<>();
// iterate over all higher roles from rolesReachableInOneStepMap
for (String roleName : hierarchy.keySet()) {
Set<GrantedAuthority> rolesToVisitSet = new HashSet<>(hierarchy.get(roleName));
for (String roleName : this.rolesReachableInOneStepMap.keySet()) {
Set<GrantedAuthority> rolesToVisitSet = new HashSet<>(this.rolesReachableInOneStepMap.get(roleName));
Set<GrantedAuthority> visitedRolesSet = new HashSet<>();
while (!rolesToVisitSet.isEmpty()) {
// take a role from the rolesToVisit set
GrantedAuthority lowerRole = rolesToVisitSet.iterator().next();
rolesToVisitSet.remove(lowerRole);
if (!visitedRolesSet.add(lowerRole) || !hierarchy.containsKey(lowerRole.getAuthority())) {
if (!visitedRolesSet.add(lowerRole)
|| !this.rolesReachableInOneStepMap.containsKey(lowerRole.getAuthority())) {
continue; // Already visited role or role with missing hierarchy
}
else if (roleName.equals(lowerRole.getAuthority())) {
throw new CycleInRoleHierarchyException();
}
rolesToVisitSet.addAll(hierarchy.get(lowerRole.getAuthority()));
rolesToVisitSet.addAll(this.rolesReachableInOneStepMap.get(lowerRole.getAuthority()));
}
rolesReachableInOneOrMoreStepsMap.put(roleName, visitedRolesSet);
this.rolesReachableInOneOrMoreStepsMap.put(roleName, visitedRolesSet);
logger.debug(LogMessage.format(
"buildRolesReachableInOneOrMoreStepsMap() - From role %s one can reach %s in one or more steps.",
roleName, visitedRolesSet));
}
return rolesReachableInOneOrMoreStepsMap;
}
/**
* Builder class for constructing a {@link RoleHierarchyImpl} based on a hierarchical
* role structure.
*
* @author Federico Herrera
* @since 6.3
*/
public static final class Builder {
private final String rolePrefix;
private final Map<String, Set<GrantedAuthority>> hierarchy;
private Builder(String rolePrefix) {
this.rolePrefix = rolePrefix;
this.hierarchy = new LinkedHashMap<>();
}
/**
* Creates a new hierarchy branch to define a role and its child roles.
* @param role the highest role in this branch
* @return a {@link ImpliedRoles} to define the child roles for the
* <code>role</code>
*/
public ImpliedRoles role(String role) {
Assert.hasText(role, "role must not be empty");
return new ImpliedRoles(role);
}
/**
* Builds and returns a {@link RoleHierarchyImpl} describing the defined role
* hierarchy.
* @return a {@link RoleHierarchyImpl}
*/
public RoleHierarchyImpl build() {
return new RoleHierarchyImpl(this.hierarchy);
}
private Builder addHierarchy(String role, String... impliedRoles) {
Set<GrantedAuthority> withPrefix = new HashSet<>();
for (String impliedRole : impliedRoles) {
withPrefix.add(new SimpleGrantedAuthority(this.rolePrefix.concat(impliedRole)));
}
this.hierarchy.put(this.rolePrefix.concat(role), withPrefix);
return this;
}
/**
* Builder class for constructing child roles within a role hierarchy branch.
*/
public final class ImpliedRoles {
private final String role;
private ImpliedRoles(String role) {
this.role = role;
}
/**
* Specifies implied role(s) for the current role in the hierarchy.
* @param impliedRoles role name(s) implied by the role.
* @return the same {@link Builder} instance
* @throws IllegalArgumentException if <code>impliedRoles</code> is null,
* empty or contains any null element.
*/
public Builder implies(String... impliedRoles) {
Assert.notEmpty(impliedRoles, "at least one implied role must be provided");
Assert.noNullElements(impliedRoles, "implied role name(s) cannot be empty");
return Builder.this.addHierarchy(this.role, impliedRoles);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2016 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2016 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.

View File

@@ -25,10 +25,6 @@ import reactor.core.scheduler.Schedulers;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.password.CompromisedPasswordCheckResult;
import org.springframework.security.authentication.password.CompromisedPasswordChecker;
import org.springframework.security.authentication.password.CompromisedPasswordException;
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
@@ -68,8 +64,6 @@ public abstract class AbstractUserDetailsReactiveAuthenticationManager
private UserDetailsChecker postAuthenticationChecks = this::defaultPostAuthenticationChecks;
private ReactiveCompromisedPasswordChecker compromisedPasswordChecker;
private void defaultPreAuthenticationChecks(UserDetails user) {
if (!user.isAccountNonLocked()) {
this.logger.debug("User account is locked");
@@ -106,23 +100,12 @@ public abstract class AbstractUserDetailsReactiveAuthenticationManager
.publishOn(this.scheduler)
.filter((userDetails) -> this.passwordEncoder.matches(presentedPassword, userDetails.getPassword()))
.switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
.flatMap((userDetails) -> checkCompromisedPassword(presentedPassword).thenReturn(userDetails))
.flatMap((userDetails) -> upgradeEncodingIfNecessary(userDetails, presentedPassword))
.doOnNext(this.postAuthenticationChecks::check)
.map(this::createUsernamePasswordAuthenticationToken);
// @formatter:on
}
private Mono<Void> checkCompromisedPassword(String password) {
if (this.compromisedPasswordChecker == null) {
return Mono.empty();
}
return this.compromisedPasswordChecker.check(password)
.filter(CompromisedPasswordCheckResult::isCompromised)
.flatMap((compromised) -> Mono.error(new CompromisedPasswordException(
"The provided password is compromised, please change your password")));
}
private Mono<UserDetails> upgradeEncodingIfNecessary(UserDetails userDetails, String presentedPassword) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(userDetails.getPassword());
@@ -193,16 +176,6 @@ public abstract class AbstractUserDetailsReactiveAuthenticationManager
this.messages = new MessageSourceAccessor(messageSource);
}
/**
* Sets the {@link ReactiveCompromisedPasswordChecker} to be used before creating a
* successful authentication. Defaults to {@code null}.
* @param compromisedPasswordChecker the {@link CompromisedPasswordChecker} to use
* @since 6.3
*/
public void setCompromisedPasswordChecker(ReactiveCompromisedPasswordChecker compromisedPasswordChecker) {
this.compromisedPasswordChecker = compromisedPasswordChecker;
}
/**
* Allows subclasses to retrieve the <code>UserDetails</code> from an
* implementation-specific location.

View File

@@ -47,10 +47,10 @@ public interface AuthenticationProvider {
* <p>
* Returning <code>true</code> does not guarantee an
* <code>AuthenticationProvider</code> will be able to authenticate the presented
* <code>Authentication</code> object. It simply indicates it can support closer
* evaluation of it. An <code>AuthenticationProvider</code> can still return
* <code>null</code> from the {@link #authenticate(Authentication)} method to indicate
* another <code>AuthenticationProvider</code> should be tried.
* instance of the <code>Authentication</code> class. It simply indicates it can
* support closer evaluation of it. An <code>AuthenticationProvider</code> can still
* return <code>null</code> from the {@link #authenticate(Authentication)} method to
* indicate another <code>AuthenticationProvider</code> should be tried.
* </p>
* <p>
* Selection of an <code>AuthenticationProvider</code> capable of performing

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 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.
@@ -23,33 +23,6 @@ import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.util.Assert;
/**
* Implementation of {@link UserDetailsService} that utilizes caching through a
* {@link UserCache}
* <p>
* If a null {@link UserDetails} instance is returned from
* {@link UserCache#getUserFromCache(String)} to the {@link UserCache} got from
* {@link #getUserCache()}, the user load is deferred to the {@link UserDetailsService}
* provided during construction. Otherwise, the instance retrieved from the cache is
* returned.
* <p>
* It is initialized with a {@link NullUserCache} by default, so it's strongly recommended
* setting your own {@link UserCache} using {@link #setUserCache(UserCache)}, otherwise,
* the delegate will be called every time.
* <p>
* Utilize this class by defining a {@link org.springframework.context.annotation.Bean}
* that encapsulates an actual implementation of {@link UserDetailsService} and providing
* a {@link UserCache} implementation.
* </p>
* For example: <pre>
* &#64;Bean
* public CachingUserDetailsService cachingUserDetailsService(UserCache userCache) {
* UserDetailsService delegate = ...;
* CachingUserDetailsService service = new CachingUserDetailsService(delegate);
* service.setUserCache(userCache);
* return service;
* }
* </pre>
*
* @author Luke Taylor
* @since 2.0
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2018 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.
@@ -18,10 +18,7 @@ package org.springframework.security.authentication;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -30,9 +27,8 @@ import org.springframework.util.Assert;
/**
* A {@link ReactiveAuthenticationManager} that delegates to other
* {@link ReactiveAuthenticationManager} instances. When {@code continueOnError} is
* {@code true}, will continue until the first non-empty, non-error result; otherwise,
* will continue only until the first non-empty result.
* {@link ReactiveAuthenticationManager} instances using the result from the first non
* empty result.
*
* @author Rob Winch
* @since 5.1
@@ -41,10 +37,6 @@ public class DelegatingReactiveAuthenticationManager implements ReactiveAuthenti
private final List<ReactiveAuthenticationManager> delegates;
private boolean continueOnError = false;
private final Log logger = LogFactory.getLog(getClass());
public DelegatingReactiveAuthenticationManager(ReactiveAuthenticationManager... entryPoints) {
this(Arrays.asList(entryPoints));
}
@@ -56,20 +48,11 @@ public class DelegatingReactiveAuthenticationManager implements ReactiveAuthenti
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
Flux<ReactiveAuthenticationManager> result = Flux.fromIterable(this.delegates);
Function<ReactiveAuthenticationManager, Mono<Authentication>> logging = (m) -> m.authenticate(authentication)
.doOnError(this.logger::debug);
return ((this.continueOnError) ? result.concatMapDelayError(logging) : result.concatMap(logging)).next();
}
/**
* Continue iterating when a delegate errors, defaults to {@code false}
* @param continueOnError whether to continue when a delegate errors
* @since 6.3
*/
public void setContinueOnError(boolean continueOnError) {
this.continueOnError = continueOnError;
// @formatter:off
return Flux.fromIterable(this.delegates)
.concatMap((m) -> m.authenticate(authentication))
.next();
// @formatter:on
}
}

View File

@@ -20,8 +20,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.password.CompromisedPasswordChecker;
import org.springframework.security.authentication.password.CompromisedPasswordException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
@@ -62,8 +60,6 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
private UserDetailsPasswordService userDetailsPasswordService;
private CompromisedPasswordChecker compromisedPasswordChecker;
public DaoAuthenticationProvider() {
this(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
@@ -126,15 +122,10 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
@Override
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
UserDetails user) {
String presentedPassword = authentication.getCredentials().toString();
boolean isPasswordCompromised = this.compromisedPasswordChecker != null
&& this.compromisedPasswordChecker.check(presentedPassword).isCompromised();
if (isPasswordCompromised) {
throw new CompromisedPasswordException("The provided password is compromised, please change your password");
}
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
@@ -183,14 +174,4 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
this.userDetailsPasswordService = userDetailsPasswordService;
}
/**
* Sets the {@link CompromisedPasswordChecker} to be used before creating a successful
* authentication. Defaults to {@code null}.
* @param compromisedPasswordChecker the {@link CompromisedPasswordChecker} to use
* @since 6.3
*/
public void setCompromisedPasswordChecker(CompromisedPasswordChecker compromisedPasswordChecker) {
this.compromisedPasswordChecker = compromisedPasswordChecker;
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2002-2024 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.authentication.password;
public class CompromisedPasswordCheckResult {
private final boolean compromised;
public CompromisedPasswordCheckResult(boolean compromised) {
this.compromised = compromised;
}
public boolean isCompromised() {
return this.compromised;
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2002-2024 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.authentication.password;
import org.springframework.lang.NonNull;
/**
* An API for checking if a password has been compromised.
*
* @author Marcus da Coregio
* @since 6.3
*/
public interface CompromisedPasswordChecker {
/**
* Check whether the password is compromised
* @param password the password to check
* @return a non-null {@link CompromisedPasswordCheckResult}
*/
@NonNull
CompromisedPasswordCheckResult check(String password);
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2002-2024 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.authentication.password;
import org.springframework.security.core.AuthenticationException;
/**
* Indicates that the provided password is compromised
*
* @author Marcus da Coregio
* @since 6.3
*/
public class CompromisedPasswordException extends AuthenticationException {
public CompromisedPasswordException(String message) {
super(message);
}
public CompromisedPasswordException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2002-2024 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.authentication.password;
import reactor.core.publisher.Mono;
/**
* A Reactive API for checking if a password has been compromised.
*
* @author Marcus da Coregio
* @since 6.3
*/
public interface ReactiveCompromisedPasswordChecker {
/**
* Check whether the password is compromised
* @param password the password to check
* @return a {@link Mono} containing the {@link CompromisedPasswordCheckResult}
*/
Mono<CompromisedPasswordCheckResult> check(String password);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 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.
@@ -20,7 +20,7 @@ package org.springframework.security.authorization;
* @author Rob Winch
* @since 5.0
*/
public class AuthorizationDecision implements AuthorizationResult {
public class AuthorizationDecision {
private final boolean granted;
@@ -28,7 +28,6 @@ public class AuthorizationDecision implements AuthorizationResult {
this.granted = granted;
}
@Override
public boolean isGranted() {
return this.granted;
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2002-2024 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 org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert;
/**
* An {@link AccessDeniedException} that contains the {@link AuthorizationResult}
*
* @author Marcus da Coregio
* @since 6.3
*/
public class AuthorizationDeniedException extends AccessDeniedException implements AuthorizationResult {
private final AuthorizationResult result;
public AuthorizationDeniedException(String msg, AuthorizationResult authorizationResult) {
super(msg);
Assert.notNull(authorizationResult, "authorizationResult cannot be null");
Assert.isTrue(!authorizationResult.isGranted(), "Granted authorization results are not supported");
this.result = authorizationResult;
}
public AuthorizationResult getAuthorizationResult() {
return this.result;
}
@Override
public boolean isGranted() {
return false;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 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.
@@ -23,7 +23,6 @@ import java.util.List;
* A factory class to create an {@link AuthorizationManager} instances.
*
* @author Evgeniy Cheban
* @author Josh Cummings
* @since 5.8
*/
public final class AuthorizationManagers {
@@ -38,23 +37,6 @@ public final class AuthorizationManagers {
*/
@SafeVarargs
public static <T> AuthorizationManager<T> anyOf(AuthorizationManager<T>... managers) {
return anyOf(new AuthorizationDecision(false), managers);
}
/**
* Creates an {@link AuthorizationManager} that grants access if at least one
* {@link AuthorizationManager} granted, if <code>managers</code> are empty or
* abstained, a default {@link AuthorizationDecision} is returned.
* @param <T> the type of object that is being authorized
* @param allAbstainDefaultDecision the default decision if all
* {@link AuthorizationManager}s abstained
* @param managers the {@link AuthorizationManager}s to use
* @return the {@link AuthorizationManager} to use
* @since 6.3
*/
@SafeVarargs
public static <T> AuthorizationManager<T> anyOf(AuthorizationDecision allAbstainDefaultDecision,
AuthorizationManager<T>... managers) {
return (authentication, object) -> {
List<AuthorizationDecision> decisions = new ArrayList<>();
for (AuthorizationManager<T> manager : managers) {
@@ -68,7 +50,7 @@ public final class AuthorizationManagers {
decisions.add(decision);
}
if (decisions.isEmpty()) {
return allAbstainDefaultDecision;
return new AuthorizationDecision(false);
}
return new CompositeAuthorizationDecision(false, decisions);
};
@@ -84,23 +66,6 @@ public final class AuthorizationManagers {
*/
@SafeVarargs
public static <T> AuthorizationManager<T> allOf(AuthorizationManager<T>... managers) {
return allOf(new AuthorizationDecision(true), managers);
}
/**
* Creates an {@link AuthorizationManager} that grants access if all
* {@link AuthorizationManager}s granted, if <code>managers</code> are empty or
* abstained, a default {@link AuthorizationDecision} is returned.
* @param <T> the type of object that is being authorized
* @param allAbstainDefaultDecision the default decision if all
* {@link AuthorizationManager}s abstained
* @param managers the {@link AuthorizationManager}s to use
* @return the {@link AuthorizationManager} to use
* @since 6.3
*/
@SafeVarargs
public static <T> AuthorizationManager<T> allOf(AuthorizationDecision allAbstainDefaultDecision,
AuthorizationManager<T>... managers) {
return (authentication, object) -> {
List<AuthorizationDecision> decisions = new ArrayList<>();
for (AuthorizationManager<T> manager : managers) {
@@ -114,31 +79,12 @@ public final class AuthorizationManagers {
decisions.add(decision);
}
if (decisions.isEmpty()) {
return allAbstainDefaultDecision;
return new AuthorizationDecision(true);
}
return new CompositeAuthorizationDecision(true, decisions);
};
}
/**
* Creates an {@link AuthorizationManager} that reverses whatever decision the given
* {@link AuthorizationManager} granted. If the given {@link AuthorizationManager}
* abstains, then the returned manager also abstains.
* @param <T> the type of object that is being authorized
* @param manager the {@link AuthorizationManager} to reverse
* @return the reversing {@link AuthorizationManager}
* @since 6.3
*/
public static <T> AuthorizationManager<T> not(AuthorizationManager<T> manager) {
return (authentication, object) -> {
AuthorizationDecision decision = manager.check(authentication, object);
if (decision == null) {
return null;
}
return new NotAuthorizationDecision(decision);
};
}
private AuthorizationManagers() {
}
@@ -158,20 +104,4 @@ public final class AuthorizationManagers {
}
private static final class NotAuthorizationDecision extends AuthorizationDecision {
private final AuthorizationDecision decision;
private NotAuthorizationDecision(AuthorizationDecision decision) {
super(!decision.isGranted());
this.decision = decision;
}
@Override
public String toString() {
return "NotAuthorizationDecision [decision=" + this.decision + ']';
}
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2002-2024 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;
/**
* A factory for wrapping arbitrary objects in authorization-related advice
*
* @author Josh Cummings
* @since 6.3
* @see org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory
*/
public interface AuthorizationProxyFactory {
/**
* Wrap the given {@code object} in authorization-related advice.
*
* <p>
* Please check the implementation for which kinds of objects it supports.
* @param object the object to proxy
* @return the proxied object
* @throws org.springframework.aop.framework.AopConfigException if a proxy cannot be
* created
*/
Object proxy(Object object);
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright 2002-2024 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;
/**
* Represents an authorization result
*
* @author Marcus da Coregio
* @since 6.3
*/
public interface AuthorizationResult {
/**
* @return whether the access has been granted
*/
boolean isGranted();
}

View File

@@ -21,15 +21,11 @@ import java.util.function.Supplier;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.util.Assert;
@@ -40,8 +36,7 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 6.0
*/
public final class ObservationAuthorizationManager<T>
implements AuthorizationManager<T>, MessageSourceAware, MethodAuthorizationDeniedHandler {
public final class ObservationAuthorizationManager<T> implements AuthorizationManager<T>, MessageSourceAware {
private final ObservationRegistry registry;
@@ -51,14 +46,9 @@ public final class ObservationAuthorizationManager<T>
private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager<T> delegate) {
this.registry = registry;
this.delegate = delegate;
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
}
@Override
@@ -108,15 +98,4 @@ public final class ObservationAuthorizationManager<T>
this.messages = new MessageSourceAccessor(messageSource);
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
}
}

View File

@@ -20,13 +20,9 @@ import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@@ -36,8 +32,7 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 6.0
*/
public final class ObservationReactiveAuthorizationManager<T>
implements ReactiveAuthorizationManager<T>, MethodAuthorizationDeniedHandler {
public final class ObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
private final ObservationRegistry registry;
@@ -45,15 +40,10 @@ public final class ObservationReactiveAuthorizationManager<T>
private ObservationConvention<AuthorizationObservationContext<?>> convention = new AuthorizationObservationConvention();
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
public ObservationReactiveAuthorizationManager(ObservationRegistry registry,
ReactiveAuthorizationManager<T> delegate) {
this.registry = registry;
this.delegate = delegate;
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
}
@Override
@@ -91,15 +81,4 @@ public final class ObservationReactiveAuthorizationManager<T>
this.convention = convention;
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 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.
@@ -16,35 +16,24 @@
package org.springframework.security.authorization.method;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.NonNull;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.util.Assert;
/**
* For internal use only, as this contract is likely to change
*
* @author Evgeniy Cheban
* @author DingHao
*/
abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute> {
private final Map<MethodClassKey, T> cachedAttributes = new ConcurrentHashMap<>();
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
private PrePostTemplateDefaults defaults;
/**
* Returns an {@link ExpressionAttribute} for the {@link MethodInvocation}.
* @param mi the {@link MethodInvocation} to use
@@ -68,28 +57,6 @@ abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute
return this.cachedAttributes.computeIfAbsent(cacheKey, (k) -> resolveAttribute(method, targetClass));
}
final <A extends Annotation> Function<AnnotatedElement, A> findUniqueAnnotation(Class<A> type) {
return (this.defaults != null) ? AuthorizationAnnotationUtils.withDefaults(type, this.defaults)
: AuthorizationAnnotationUtils.withDefaults(type);
}
/**
* Returns the {@link MethodSecurityExpressionHandler}.
* @return the {@link MethodSecurityExpressionHandler} to use
*/
MethodSecurityExpressionHandler getExpressionHandler() {
return this.expressionHandler;
}
void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
}
void setTemplateDefaults(PrePostTemplateDefaults defaults) {
this.defaults = defaults;
}
/**
* Subclasses should implement this method to provide the non-null
* {@link ExpressionAttribute} for the method and the target class.
@@ -100,8 +67,4 @@ abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute
@NonNull
abstract T resolveAttribute(Method method, Class<?> targetClass);
Class<?> targetClass(Method method, Class<?> targetClass) {
return (targetClass != null) ? targetClass : method.getDeclaringClass();
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.Ordered;
/**
* An interface that indicates method security advice
*
* @author Josh Cummings
* @since 6.3
* @see AuthorizationManagerBeforeMethodInterceptor
* @see AuthorizationManagerAfterMethodInterceptor
* @see PreFilterAuthorizationMethodInterceptor
* @see PostFilterAuthorizationMethodInterceptor
*/
public interface AuthorizationAdvisor extends Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
}

View File

@@ -1,542 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Stream;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.NonNull;
import org.springframework.security.authorization.AuthorizationProxyFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* A proxy factory for applying authorization advice to an arbitrary object.
*
* <p>
* For example, consider a non-Spring-managed object {@code Foo}: <pre>
* class Foo {
* &#064;PreAuthorize("hasAuthority('bar:read')")
* String bar() { ... }
* }
* </pre>
*
* Use {@link AuthorizationAdvisorProxyFactory} to wrap the instance in Spring Security's
* {@link org.springframework.security.access.prepost.PreAuthorize} method interceptor
* like so:
*
* <pre>
* AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
* Foo foo = new Foo();
* foo.bar(); // passes
* Foo securedFoo = proxyFactory.proxy(foo);
* securedFoo.bar(); // access denied!
* </pre>
*
* @author Josh Cummings
* @since 6.3
*/
public final class AuthorizationAdvisorProxyFactory
implements AuthorizationProxyFactory, Iterable<AuthorizationAdvisor> {
private static final boolean isReactivePresent = ClassUtils.isPresent("reactor.core.publisher.Mono", null);
private static final TargetVisitor DEFAULT_VISITOR = isReactivePresent
? TargetVisitor.of(new ClassVisitor(), new ReactiveTypeVisitor(), new ContainerTypeVisitor())
: TargetVisitor.of(new ClassVisitor(), new ContainerTypeVisitor());
private static final TargetVisitor DEFAULT_VISITOR_SKIP_VALUE_TYPES = TargetVisitor.of(new ClassVisitor(),
new IgnoreValueTypeVisitor(), DEFAULT_VISITOR);
private List<AuthorizationAdvisor> advisors;
private TargetVisitor visitor = DEFAULT_VISITOR;
private AuthorizationAdvisorProxyFactory(List<AuthorizationAdvisor> advisors) {
this.advisors = new ArrayList<>(advisors);
this.advisors.add(new AuthorizeReturnObjectMethodInterceptor(this));
setAdvisors(this.advisors);
}
/**
* Construct an {@link AuthorizationAdvisorProxyFactory} with the defaults needed for
* wrapping objects in Spring Security's pre-post method security support.
* @return an {@link AuthorizationAdvisorProxyFactory} for adding pre-post method
* security support
*/
public static AuthorizationAdvisorProxyFactory withDefaults() {
List<AuthorizationAdvisor> advisors = new ArrayList<>();
advisors.add(AuthorizationManagerBeforeMethodInterceptor.preAuthorize());
advisors.add(AuthorizationManagerAfterMethodInterceptor.postAuthorize());
advisors.add(new PreFilterAuthorizationMethodInterceptor());
advisors.add(new PostFilterAuthorizationMethodInterceptor());
return new AuthorizationAdvisorProxyFactory(advisors);
}
/**
* Construct an {@link AuthorizationAdvisorProxyFactory} with the defaults needed for
* wrapping objects in Spring Security's pre-post reactive method security support.
* @return an {@link AuthorizationAdvisorProxyFactory} for adding pre-post reactive
* method security support
*/
public static AuthorizationAdvisorProxyFactory withReactiveDefaults() {
List<AuthorizationAdvisor> advisors = new ArrayList<>();
advisors.add(AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize());
advisors.add(AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize());
advisors.add(new PreFilterAuthorizationReactiveMethodInterceptor());
advisors.add(new PostFilterAuthorizationReactiveMethodInterceptor());
return new AuthorizationAdvisorProxyFactory(advisors);
}
/**
* Proxy an object to enforce authorization advice.
*
* <p>
* Proxies any instance of a non-final class or a class that implements more than one
* interface.
*
* <p>
* If {@code target} is an {@link Iterator}, {@link Collection}, {@link Array},
* {@link Map}, {@link Stream}, or {@link Optional}, then the element or value type is
* proxied.
*
* <p>
* If {@code target} is a {@link Class}, then {@link ProxyFactory#getProxyClass} is
* invoked instead.
* @param target the instance to proxy
* @return the proxied instance
*/
@Override
public Object proxy(Object target) {
if (target == null) {
return null;
}
Object proxied = this.visitor.visit(this, target);
if (proxied != null) {
return proxied;
}
ProxyFactory factory = new ProxyFactory(target);
for (Advisor advisor : this.advisors) {
factory.addAdvisors(advisor);
}
factory.setProxyTargetClass(!Modifier.isFinal(target.getClass().getModifiers()));
return factory.getProxy();
}
/**
* Add advisors that should be included to each proxy created.
*
* <p>
* All advisors are re-sorted by their advisor order.
* @param advisors the advisors to add
*/
public void setAdvisors(AuthorizationAdvisor... advisors) {
this.advisors = new ArrayList<>(List.of(advisors));
AnnotationAwareOrderComparator.sort(this.advisors);
}
/**
* Add advisors that should be included to each proxy created.
*
* <p>
* All advisors are re-sorted by their advisor order.
* @param advisors the advisors to add
*/
public void setAdvisors(Collection<AuthorizationAdvisor> advisors) {
this.advisors = new ArrayList<>(advisors);
AnnotationAwareOrderComparator.sort(this.advisors);
}
/**
* Use this visitor to navigate the proxy target's hierarchy.
*
* <p>
* This can be helpful when you want a specialized behavior for a type or set of
* types. For example, if you want to have this factory skip primitives and wrappers,
* then you can do:
*
* <pre>
* AuthorizationAdvisorProxyFactory proxyFactory = new AuthorizationAdvisorProxyFactory();
* proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
* </pre>
*
* <p>
* The default {@link TargetVisitor} proxies {@link Class} instances as well as
* instances contained in reactive types (if reactor is present), collection types,
* and other container types like {@link Optional} and {@link Supplier}.
*
* <p>
* If you want to add support for another container type, you can do so in the
* following way:
*
* <pre>
* TargetVisitor functions = (factory, target) -> {
* if (target instanceof Function function) {
* return (input) -> factory.proxy(function.apply(input));
* }
* return null;
* };
* AuthorizationAdvisorProxyFactory proxyFactory = new AuthorizationAdvisorProxyFactory();
* proxyFactory.setTargetVisitor(TargetVisitor.of(functions, TargetVisitor.defaultsSkipValueTypes()));
* </pre>
* @param visitor the visitor to use to introduce specialized behavior for a type
* @see TargetVisitor#defaults
*/
public void setTargetVisitor(TargetVisitor visitor) {
Assert.notNull(visitor, "delegate cannot be null");
this.visitor = visitor;
}
@Override
@NonNull
public Iterator<AuthorizationAdvisor> iterator() {
return this.advisors.iterator();
}
/**
* An interface to handle how the {@link AuthorizationAdvisorProxyFactory} should step
* through the target's object hierarchy.
*
* @author Josh Cummings
* @since 6.3
* @see AuthorizationAdvisorProxyFactory#setTargetVisitor
*/
public interface TargetVisitor {
/**
* Visit and possibly proxy this object.
*
* <p>
* Visiting may take the form of walking down this object's hierarchy and proxying
* sub-objects.
*
* <p>
* An example is a visitor that proxies the elements of a {@link List} instead of
* the list itself
*
* <p>
* Returning {@code null} implies that this visitor does not want to proxy this
* object
* @param proxyFactory the proxy factory to delegate proxying to for any
* sub-objects
* @param target the object to proxy
* @return the visited (and possibly proxied) object
*/
Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target);
/**
* The default {@link TargetVisitor}, which will proxy {@link Class} instances as
* well as instances contained in reactive types (if reactor is present),
* collection types, and other container types like {@link Optional} and
* {@link Supplier}
*/
static TargetVisitor defaults() {
return AuthorizationAdvisorProxyFactory.DEFAULT_VISITOR;
}
/**
* The default {@link TargetVisitor} that also skips any value types (for example,
* {@link String}, {@link Integer}). This is handy for annotations like
* {@link AuthorizeReturnObject} when used at the class level
*/
static TargetVisitor defaultsSkipValueTypes() {
return AuthorizationAdvisorProxyFactory.DEFAULT_VISITOR_SKIP_VALUE_TYPES;
}
/**
* Compose a set of visitors. This is helpful when you are customizing for a given
* type and still want the defaults applied for the remaining types.
*
* <p>
* The resulting visitor will execute the first visitor that returns a non-null
* value.
* @param visitors the set of visitors
* @return a composite that executes the first visitor that returns a non-null
* value
*/
static TargetVisitor of(TargetVisitor... visitors) {
return (proxyFactory, target) -> {
for (TargetVisitor visitor : visitors) {
Object result = visitor.visit(proxyFactory, target);
if (result != null) {
return result;
}
}
return null;
};
}
}
private static final class IgnoreValueTypeVisitor implements TargetVisitor {
@Override
public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object object) {
if (ClassUtils.isSimpleValueType(object.getClass())) {
return object;
}
return null;
}
}
private static final class ClassVisitor implements TargetVisitor {
@Override
public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object object) {
if (object instanceof Class<?> targetClass) {
ProxyFactory factory = new ProxyFactory();
factory.setTargetClass(targetClass);
factory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass));
factory.setProxyTargetClass(!Modifier.isFinal(targetClass.getModifiers()));
for (Advisor advisor : proxyFactory) {
factory.addAdvisors(advisor);
}
return factory.getProxyClass(getClass().getClassLoader());
}
return null;
}
}
private static final class ContainerTypeVisitor implements TargetVisitor {
@Override
public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) {
if (target instanceof Iterator<?> iterator) {
return proxyIterator(proxyFactory, iterator);
}
if (target instanceof Queue<?> queue) {
return proxyQueue(proxyFactory, queue);
}
if (target instanceof List<?> list) {
return proxyList(proxyFactory, list);
}
if (target instanceof SortedSet<?> set) {
return proxySortedSet(proxyFactory, set);
}
if (target instanceof Set<?> set) {
return proxySet(proxyFactory, set);
}
if (target.getClass().isArray()) {
return proxyArray(proxyFactory, (Object[]) target);
}
if (target instanceof SortedMap<?, ?> map) {
return proxySortedMap(proxyFactory, map);
}
if (target instanceof Iterable<?> iterable) {
return proxyIterable(proxyFactory, iterable);
}
if (target instanceof Map<?, ?> map) {
return proxyMap(proxyFactory, map);
}
if (target instanceof Stream<?> stream) {
return proxyStream(proxyFactory, stream);
}
if (target instanceof Optional<?> optional) {
return proxyOptional(proxyFactory, optional);
}
if (target instanceof Supplier<?> supplier) {
return proxySupplier(proxyFactory, supplier);
}
return null;
}
@SuppressWarnings("unchecked")
private <T> T proxyCast(AuthorizationProxyFactory proxyFactory, T target) {
return (T) proxyFactory.proxy(target);
}
private <T> Iterable<T> proxyIterable(AuthorizationProxyFactory proxyFactory, Iterable<T> iterable) {
return () -> proxyIterator(proxyFactory, iterable.iterator());
}
private <T> Iterator<T> proxyIterator(AuthorizationProxyFactory proxyFactory, Iterator<T> iterator) {
return new Iterator<>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public T next() {
return proxyCast(proxyFactory, iterator.next());
}
};
}
private <T> SortedSet<T> proxySortedSet(AuthorizationProxyFactory proxyFactory, SortedSet<T> set) {
SortedSet<T> proxies = new TreeSet<>(set.comparator());
for (T toProxy : set) {
proxies.add(proxyCast(proxyFactory, toProxy));
}
try {
set.clear();
set.addAll(proxies);
return proxies;
}
catch (UnsupportedOperationException ex) {
return Collections.unmodifiableSortedSet(proxies);
}
}
private <T> Set<T> proxySet(AuthorizationProxyFactory proxyFactory, Set<T> set) {
Set<T> proxies = new LinkedHashSet<>(set.size());
for (T toProxy : set) {
proxies.add(proxyCast(proxyFactory, toProxy));
}
try {
set.clear();
set.addAll(proxies);
return proxies;
}
catch (UnsupportedOperationException ex) {
return Collections.unmodifiableSet(proxies);
}
}
private <T> Queue<T> proxyQueue(AuthorizationProxyFactory proxyFactory, Queue<T> queue) {
Queue<T> proxies = new LinkedList<>();
for (T toProxy : queue) {
proxies.add(proxyCast(proxyFactory, toProxy));
}
queue.clear();
queue.addAll(proxies);
return proxies;
}
private <T> List<T> proxyList(AuthorizationProxyFactory proxyFactory, List<T> list) {
List<T> proxies = new ArrayList<>(list.size());
for (T toProxy : list) {
proxies.add(proxyCast(proxyFactory, toProxy));
}
try {
list.clear();
list.addAll(proxies);
return proxies;
}
catch (UnsupportedOperationException ex) {
return Collections.unmodifiableList(proxies);
}
}
private Object[] proxyArray(AuthorizationProxyFactory proxyFactory, Object[] objects) {
List<Object> retain = new ArrayList<>(objects.length);
for (Object object : objects) {
retain.add(proxyFactory.proxy(object));
}
Object[] proxies = (Object[]) Array.newInstance(objects.getClass().getComponentType(), retain.size());
for (int i = 0; i < retain.size(); i++) {
proxies[i] = retain.get(i);
}
return proxies;
}
private <K, V> SortedMap<K, V> proxySortedMap(AuthorizationProxyFactory proxyFactory, SortedMap<K, V> entries) {
SortedMap<K, V> proxies = new TreeMap<>(entries.comparator());
for (Map.Entry<K, V> entry : entries.entrySet()) {
proxies.put(entry.getKey(), proxyCast(proxyFactory, entry.getValue()));
}
try {
entries.clear();
entries.putAll(proxies);
return entries;
}
catch (UnsupportedOperationException ex) {
return Collections.unmodifiableSortedMap(proxies);
}
}
private <K, V> Map<K, V> proxyMap(AuthorizationProxyFactory proxyFactory, Map<K, V> entries) {
Map<K, V> proxies = new LinkedHashMap<>(entries.size());
for (Map.Entry<K, V> entry : entries.entrySet()) {
proxies.put(entry.getKey(), proxyCast(proxyFactory, entry.getValue()));
}
try {
entries.clear();
entries.putAll(proxies);
return entries;
}
catch (UnsupportedOperationException ex) {
return Collections.unmodifiableMap(proxies);
}
}
private Stream<?> proxyStream(AuthorizationProxyFactory proxyFactory, Stream<?> stream) {
return stream.map(proxyFactory::proxy).onClose(stream::close);
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private Optional<?> proxyOptional(AuthorizationProxyFactory proxyFactory, Optional<?> optional) {
return optional.map(proxyFactory::proxy);
}
private Supplier<?> proxySupplier(AuthorizationProxyFactory proxyFactory, Supplier<?> supplier) {
return () -> proxyFactory.proxy(supplier.get());
}
}
private static class ReactiveTypeVisitor implements TargetVisitor {
@Override
@SuppressWarnings("ReactiveStreamsUnusedPublisher")
public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) {
if (target instanceof Mono<?> mono) {
return proxyMono(proxyFactory, mono);
}
if (target instanceof Flux<?> flux) {
return proxyFlux(proxyFactory, flux);
}
return null;
}
private Mono<?> proxyMono(AuthorizationProxyFactory proxyFactory, Mono<?> mono) {
return mono.map(proxyFactory::proxy);
}
private Flux<?> proxyFlux(AuthorizationProxyFactory proxyFactory, Flux<?> flux) {
return flux.map(proxyFactory::proxy);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -17,100 +17,56 @@
package org.springframework.security.authorization.method;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.annotation.RepeatableContainers;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.PropertyPlaceholderHelper;
/**
* A collection of utility methods that check for, and error on, conflicting annotations.
* This is specifically important for Spring Security annotations which are not designed
* to be repeatable.
* A wrapper around {@link AnnotationUtils} that checks for, and errors on, conflicting
* annotations. This is specifically important for Spring Security annotations which are
* not designed to be repeatable.
*
* <p>
* There are numerous ways that two annotations of the same type may be attached to the
* same method. For example, a class may implement a method defined in two separate
* interfaces. If both of those interfaces have a {@code @PreAuthorize} annotation, then
* it's unclear which {@code @PreAuthorize} expression Spring Security should use.
* interfaces. If both of those interfaces have a `@PreAuthorize` annotation, then it's
* unclear which `@PreAuthorize` expression Spring Security should use.
*
* <p>
* Another way is when one of Spring Security's annotations is used as a meta-annotation.
* In that case, two custom annotations can be declared, each with their own
* {@code @PreAuthorize} declaration. If both custom annotations are used on the same
* method, then it's unclear which {@code @PreAuthorize} expression Spring Security should
* use.
* `@PreAuthorize` declaration. If both custom annotations are used on the same method,
* then it's unclear which `@PreAuthorize` expression Spring Security should use.
*
* @author Josh Cummings
* @author Sam Brannen
*/
final class AuthorizationAnnotationUtils {
static <A extends Annotation> Function<AnnotatedElement, A> withDefaults(Class<A> type,
PrePostTemplateDefaults defaults) {
Function<MergedAnnotation<A>, A> map = (mergedAnnotation) -> {
if (mergedAnnotation.getMetaSource() == null) {
return mergedAnnotation.synthesize();
}
PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("{", "}", null,
defaults.isIgnoreUnknown());
String expression = (String) mergedAnnotation.asMap().get("value");
Map<String, Object> annotationProperties = mergedAnnotation.getMetaSource().asMap();
Map<String, String> stringProperties = new HashMap<>();
for (Map.Entry<String, Object> property : annotationProperties.entrySet()) {
String key = property.getKey();
Object value = property.getValue();
String asString = (value instanceof String) ? (String) value
: DefaultConversionService.getSharedInstance().convert(value, String.class);
stringProperties.put(key, asString);
}
AnnotatedElement annotatedElement = (AnnotatedElement) mergedAnnotation.getSource();
String value = helper.replacePlaceholders(expression, stringProperties::get);
Map<String, Object> properties = new HashMap<>(mergedAnnotation.asMap());
properties.put("value", value);
return MergedAnnotation.of(annotatedElement, type, properties).synthesize();
};
return (annotatedElement) -> findDistinctAnnotation(annotatedElement, type, map);
}
static <A extends Annotation> Function<AnnotatedElement, A> withDefaults(Class<A> type) {
return (annotatedElement) -> findDistinctAnnotation(annotatedElement, type, MergedAnnotation::synthesize);
}
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType) {
return findDistinctAnnotation(method, annotationType, MergedAnnotation::synthesize);
}
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType) {
return findDistinctAnnotation(type, annotationType, MergedAnnotation::synthesize);
}
/**
* Perform an exhaustive search on the type hierarchy of the given {@link Method} for
* the annotation of type {@code annotationType}, including any annotations using
* {@code annotationType} as a meta-annotation.
*
* <p>
* If more than one unique annotation is found, then throw an error.
* If more than one is found, then throw an error.
* @param method the method declaration to search from
* @param annotationType the annotation type to search for
* @return a unique instance of the annotation attributed to the method, {@code null}
* otherwise
* @throws AnnotationConfigurationException if more than one unique instance of the
* @return the unique instance of the annotation attributed to the method,
* {@code null} otherwise
* @throws AnnotationConfigurationException if more than one instance of the
* annotation is found
*/
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType,
Function<MergedAnnotation<A>, A> map) {
return findDistinctAnnotation(method, annotationType, map);
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType) {
MergedAnnotations mergedAnnotations = MergedAnnotations.from(method,
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
if (hasDuplicate(mergedAnnotations, annotationType)) {
throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType
+ " attributed to " + method
+ " Please remove the duplicate annotations and publish a bean to handle your authorization logic.");
}
return AnnotationUtils.findAnnotation(method, annotationType);
}
/**
@@ -118,38 +74,60 @@ final class AuthorizationAnnotationUtils {
* the annotation of type {@code annotationType}, including any annotations using
* {@code annotationType} as a meta-annotation.
*
* <p>
* If more than one unique annotation is found, then throw an error.
* If more than one is found, then throw an error.
* @param type the type to search from
* @param annotationType the annotation type to search for
* @return a unique instance of the annotation attributed to the class, {@code null}
* otherwise
* @throws AnnotationConfigurationException if more than one unique instance of the
* @return the unique instance of the annotation attributed to the method,
* {@code null} otherwise
* @throws AnnotationConfigurationException if more than one instance of the
* annotation is found
*/
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType,
Function<MergedAnnotation<A>, A> map) {
return findDistinctAnnotation(type, annotationType, map);
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType) {
MergedAnnotations mergedAnnotations = MergedAnnotations.from(type,
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
if (hasDuplicate(mergedAnnotations, annotationType)) {
throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType
+ " attributed to " + type
+ " Please remove the duplicate annotations and publish a bean to handle your authorization logic.");
}
return AnnotationUtils.findAnnotation(type, annotationType);
}
private static <A extends Annotation> A findDistinctAnnotation(AnnotatedElement annotatedElement,
Class<A> annotationType, Function<MergedAnnotation<A>, A> map) {
MergedAnnotations mergedAnnotations = MergedAnnotations.from(annotatedElement, SearchStrategy.TYPE_HIERARCHY,
RepeatableContainers.none());
List<A> annotations = mergedAnnotations.stream(annotationType)
.map(MergedAnnotation::withNonMergedAttributes)
.map(map)
.distinct()
.toList();
private static <A extends Annotation> boolean hasDuplicate(MergedAnnotations mergedAnnotations,
Class<A> annotationType) {
MergedAnnotation<Annotation> alreadyFound = null;
for (MergedAnnotation<Annotation> mergedAnnotation : mergedAnnotations) {
if (isSynthetic(mergedAnnotation.getSource())) {
continue;
}
return switch (annotations.size()) {
case 0 -> null;
case 1 -> annotations.get(0);
default -> throw new AnnotationConfigurationException("""
Please ensure there is one unique annotation of type @%s attributed to %s. \
Found %d competing annotations: %s""".formatted(annotationType.getName(), annotatedElement,
annotations.size(), annotations));
};
if (mergedAnnotation.getType() != annotationType) {
continue;
}
if (alreadyFound == null) {
alreadyFound = mergedAnnotation;
continue;
}
// https://github.com/spring-projects/spring-framework/issues/31803
if (!mergedAnnotation.getSource().equals(alreadyFound.getSource())) {
return true;
}
if (mergedAnnotation.getRoot().getType() != alreadyFound.getRoot().getType()) {
return true;
}
}
return false;
}
private static boolean isSynthetic(Object object) {
if (object instanceof Executable) {
return ((Executable) object).isSynthetic();
}
return false;
}
private AuthorizationAnnotationUtils() {

View File

@@ -43,14 +43,12 @@ public enum AuthorizationInterceptorsOrder {
JSR250,
SECURE_RESULT(450),
POST_AUTHORIZE(500),
POST_AUTHORIZE,
/**
* {@link PostFilterAuthorizationMethodInterceptor}
*/
POST_FILTER(600),
POST_FILTER,
LAST(Integer.MAX_VALUE);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -25,12 +25,14 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.Ordered;
import org.springframework.core.log.LogMessage;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
@@ -46,7 +48,8 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 5.6
*/
public final class AuthorizationManagerAfterMethodInterceptor implements AuthorizationAdvisor {
public final class AuthorizationManagerAfterMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private Supplier<SecurityContextHolderStrategy> securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy;
@@ -56,8 +59,6 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
private final AuthorizationManager<MethodInvocationResult> authorizationManager;
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
private int order;
private AuthorizationEventPublisher eventPublisher = AuthorizationManagerAfterMethodInterceptor::noPublish;
@@ -118,17 +119,9 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object result;
try {
result = mi.proceed();
}
catch (AuthorizationDeniedException ex) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handleDeniedInvocation(mi, ex);
}
return this.defaultHandler.handleDeniedInvocation(mi, ex);
}
return attemptAuthorization(mi, result);
Object result = mi.proceed();
attemptAuthorization(mi, result);
return result;
}
@Override
@@ -179,7 +172,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
this.securityContextHolderStrategy = () -> strategy;
}
private Object attemptAuthorization(MethodInvocation mi, Object result) {
private void attemptAuthorization(MethodInvocation mi, Object result) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
MethodInvocationResult object = new MethodInvocationResult(mi, result);
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, object);
@@ -187,17 +180,9 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
+ this.authorizationManager + " and decision " + decision));
return handlePostInvocationDenied(object, decision);
throw new AccessDeniedException("Access Denied");
}
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
return result;
}
private Object handlePostInvocationDenied(MethodInvocationResult mi, AuthorizationDecision decision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler deniedHandler) {
return deniedHandler.handleDeniedInvocationResult(mi, decision);
}
return this.defaultHandler.handleDeniedInvocationResult(mi, decision);
}
private Authentication getAuthentication() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -26,17 +26,16 @@ import org.aopalliance.intercept.MethodInvocation;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Signal;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@@ -49,7 +48,8 @@ import org.springframework.util.Assert;
* @author Evgeniy Cheban
* @since 5.8
*/
public final class AuthorizationManagerAfterReactiveMethodInterceptor implements AuthorizationAdvisor {
public final class AuthorizationManagerAfterReactiveMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
@@ -61,8 +61,6 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
private int order = AuthorizationInterceptorsOrder.LAST.getOrder();
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
/**
* Creates an instance for the {@link PostAuthorize} annotation.
* @return the {@link AuthorizationManagerAfterReactiveMethodInterceptor} to use
@@ -119,39 +117,27 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
+ "(for example, a Mono or Flux) or the function must be a Kotlin coroutine "
+ "in order to support Reactor Context");
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
Function<Signal<?>, Mono<?>> postAuthorize = (signal) -> {
if (signal.isOnComplete()) {
return Mono.empty();
}
if (!signal.hasError()) {
return postAuthorize(authentication, mi, signal.get());
}
if (signal.getThrowable() instanceof AuthorizationDeniedException denied) {
return postProcess(denied, mi);
}
return Mono.error(signal.getThrowable());
};
Function<Object, Mono<?>> postAuthorize = (result) -> postAuthorize(authentication, mi, result);
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type);
if (hasFlowReturnType) {
if (isSuspendingFunction) {
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
return Flux.from(publisher).materialize().flatMap(postAuthorize);
return Flux.from(publisher).flatMap(postAuthorize);
}
else {
Assert.state(adapter != null, () -> "The returnType " + type + " on " + method
+ " must have a org.springframework.core.ReactiveAdapter registered");
Flux<?> response = Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi)))
.materialize()
.flatMap(postAuthorize);
return KotlinDelegate.asFlow(response);
}
}
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
if (isMultiValue(type, adapter)) {
Flux<?> flux = Flux.from(publisher).materialize().flatMap(postAuthorize);
Flux<?> flux = Flux.from(publisher).flatMap(postAuthorize);
return (adapter != null) ? adapter.fromPublisher(flux) : flux;
}
Mono<?> mono = Mono.from(publisher).materialize().flatMap(postAuthorize);
Mono<?> mono = Mono.from(publisher).flatMap(postAuthorize);
return (adapter != null) ? adapter.fromPublisher(mono) : mono;
}
@@ -162,42 +148,9 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
return adapter != null && adapter.isMultiValue();
}
private Mono<Object> postAuthorize(Mono<Authentication> authentication, MethodInvocation mi, Object result) {
MethodInvocationResult invocationResult = new MethodInvocationResult(mi, result);
return this.authorizationManager.check(authentication, invocationResult)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.flatMap((decision) -> postProcess(decision, invocationResult));
}
private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocationResult methodInvocationResult) {
if (decision.isGranted()) {
return Mono.just(methodInvocationResult.getResult());
}
return Mono.fromSupplier(() -> {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handleDeniedInvocationResult(methodInvocationResult, decision);
}
return this.defaultHandler.handleDeniedInvocationResult(methodInvocationResult, decision);
}).flatMap((processedResult) -> {
if (Mono.class.isAssignableFrom(processedResult.getClass())) {
return (Mono<?>) processedResult;
}
return Mono.justOrEmpty(processedResult);
});
}
private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocation methodInvocation) {
return Mono.fromSupplier(() -> {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handleDeniedInvocation(methodInvocation, decision);
}
return this.defaultHandler.handleDeniedInvocation(methodInvocation, decision);
}).flatMap((processedResult) -> {
if (Mono.class.isAssignableFrom(processedResult.getClass())) {
return (Mono<?>) processedResult;
}
return Mono.justOrEmpty(processedResult);
});
private Mono<?> postAuthorize(Mono<Authentication> authentication, MethodInvocation mi, Object result) {
return this.authorizationManager.verify(authentication, new MethodInvocationResult(mi, result))
.thenReturn(result);
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -28,16 +28,17 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.Ordered;
import org.springframework.core.log.LogMessage;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -51,7 +52,8 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 5.6
*/
public final class AuthorizationManagerBeforeMethodInterceptor implements AuthorizationAdvisor {
public final class AuthorizationManagerBeforeMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private Supplier<SecurityContextHolderStrategy> securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy;
@@ -61,8 +63,6 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
private final AuthorizationManager<MethodInvocation> authorizationManager;
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
private int order = AuthorizationInterceptorsOrder.FIRST.getOrder();
private AuthorizationEventPublisher eventPublisher = AuthorizationManagerBeforeMethodInterceptor::noPublish;
@@ -194,7 +194,8 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
return attemptAuthorization(mi);
attemptAuthorization(mi);
return mi.proceed();
}
@Override
@@ -245,49 +246,16 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
this.securityContextHolderStrategy = () -> securityContextHolderStrategy;
}
private Object attemptAuthorization(MethodInvocation mi) throws Throwable {
private void attemptAuthorization(MethodInvocation mi) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
AuthorizationDecision decision;
try {
decision = this.authorizationManager.check(this::getAuthentication, mi);
}
catch (AuthorizationDeniedException denied) {
return handle(mi, denied);
}
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, mi);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, mi, decision);
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
+ this.authorizationManager + " and decision " + decision));
return handle(mi, decision);
throw new AccessDeniedException("Access Denied");
}
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
return proceed(mi);
}
private Object proceed(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (AuthorizationDeniedException ex) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handleDeniedInvocation(mi, ex);
}
return this.defaultHandler.handleDeniedInvocation(mi, ex);
}
}
private Object handle(MethodInvocation mi, AuthorizationDeniedException denied) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handleDeniedInvocation(mi, denied);
}
return this.defaultHandler.handleDeniedInvocation(mi, denied);
}
private Object handle(MethodInvocation mi, AuthorizationResult decision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handleDeniedInvocation(mi, decision);
}
return this.defaultHandler.handleDeniedInvocation(mi, decision);
}
private Authentication getAuthentication() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -27,14 +27,14 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@@ -48,7 +48,8 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 5.8
*/
public final class AuthorizationManagerBeforeReactiveMethodInterceptor implements AuthorizationAdvisor {
public final class AuthorizationManagerBeforeReactiveMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
@@ -60,8 +61,6 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
private int order = AuthorizationInterceptorsOrder.FIRST.getOrder();
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
/**
* Creates an instance for the {@link PreAuthorize} annotation.
* @return the {@link AuthorizationManagerBeforeReactiveMethodInterceptor} to use
@@ -117,67 +116,31 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
+ " must return an instance of org.reactivestreams.Publisher "
+ "(for example, a Mono or Flux) or the function must be a Kotlin coroutine "
+ "in order to support Reactor Context");
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type);
Mono<Void> preAuthorize = this.authorizationManager.verify(authentication, mi);
if (hasFlowReturnType) {
if (isSuspendingFunction) {
return preAuthorized(mi, Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)));
return preAuthorize.thenMany(Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)));
}
else {
Assert.state(adapter != null, () -> "The returnType " + type + " on " + method
+ " must have a org.springframework.core.ReactiveAdapter registered");
Flux<Object> response = preAuthorized(mi,
Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi))));
Flux<?> response = preAuthorize
.thenMany(Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi))));
return KotlinDelegate.asFlow(response);
}
}
if (isMultiValue(type, adapter)) {
Flux<?> result = preAuthorized(mi, Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)));
Publisher<?> publisher = Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi));
Flux<?> result = preAuthorize.thenMany(publisher);
return (adapter != null) ? adapter.fromPublisher(result) : result;
}
Mono<?> result = preAuthorized(mi, Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)));
Mono<?> publisher = Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi));
Mono<?> result = preAuthorize.then(publisher);
return (adapter != null) ? adapter.fromPublisher(result) : result;
}
private Flux<Object> preAuthorized(MethodInvocation mi, Flux<Object> mapping) {
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
return this.authorizationManager.check(authentication, mi)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.flatMapMany((decision) -> {
if (decision.isGranted()) {
return mapping.onErrorResume(AuthorizationDeniedException.class,
(deniedEx) -> postProcess(deniedEx, mi));
}
return postProcess(decision, mi);
});
}
private Mono<Object> preAuthorized(MethodInvocation mi, Mono<Object> mapping) {
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
return this.authorizationManager.check(authentication, mi)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.flatMap((decision) -> {
if (decision.isGranted()) {
return mapping.onErrorResume(AuthorizationDeniedException.class,
(deniedEx) -> postProcess(deniedEx, mi));
}
return postProcess(decision, mi);
});
}
private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocation mi) {
return Mono.fromSupplier(() -> {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handleDeniedInvocation(mi, decision);
}
return this.defaultHandler.handleDeniedInvocation(mi, decision);
}).flatMap((result) -> {
if (Mono.class.isAssignableFrom(result.getClass())) {
return (Mono<?>) result;
}
return Mono.justOrEmpty(result);
});
}
private boolean isMultiValue(Class<?> returnType, ReactiveAdapter adapter) {
if (Flux.class.isAssignableFrom(returnType)) {
return true;

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Wraps Spring Security method authorization advice around the return object of any
* method this annotation is applied to.
*
* <p>
* Placing this at the class level is semantically identical to placing it on each method
* in that class.
* </p>
*
* @author Josh Cummings
* @since 6.3
* @see AuthorizeReturnObjectMethodInterceptor
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface AuthorizeReturnObject {
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import java.lang.reflect.Method;
import java.util.function.Predicate;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.Pointcuts;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.security.authorization.AuthorizationProxyFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* A method interceptor that applies the given {@link AuthorizationProxyFactory} to any
* return value annotated with {@link AuthorizeReturnObject}
*
* @author Josh Cummings
* @since 6.3
* @see AuthorizationAdvisorProxyFactory
*/
public final class AuthorizeReturnObjectMethodInterceptor implements AuthorizationAdvisor {
private final AuthorizationProxyFactory authorizationProxyFactory;
private Pointcut pointcut = Pointcuts.intersection(
new MethodReturnTypePointcut(Predicate.not(ClassUtils::isVoidType)),
AuthorizationMethodPointcuts.forAnnotations(AuthorizeReturnObject.class));
private int order = AuthorizationInterceptorsOrder.SECURE_RESULT.getOrder();
public AuthorizeReturnObjectMethodInterceptor(AuthorizationProxyFactory authorizationProxyFactory) {
Assert.notNull(authorizationProxyFactory, "authorizationManager cannot be null");
this.authorizationProxyFactory = authorizationProxyFactory;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object result = mi.proceed();
if (result == null) {
return null;
}
return this.authorizationProxyFactory.proxy(result);
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
/**
* {@inheritDoc}
*/
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
public void setPointcut(Pointcut pointcut) {
this.pointcut = pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
static final class MethodReturnTypePointcut extends StaticMethodMatcherPointcut {
private final Predicate<Class<?>> returnTypeMatches;
MethodReturnTypePointcut(Predicate<Class<?>> returnTypeMatches) {
this.returnTypeMatches = returnTypeMatches;
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
return this.returnTypeMatches.test(method.getReturnType());
}
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
final class ExpressionUtils {
private ExpressionUtils() {
}
static AuthorizationResult evaluate(Expression expr, EvaluationContext ctx) {
try {
Object result = expr.getValue(ctx);
if (result instanceof AuthorizationResult decision) {
return decision;
}
if (result instanceof Boolean granted) {
return new ExpressionAuthorizationDecision(granted, expr);
}
if (result == null) {
return null;
}
throw new IllegalArgumentException(
"SpEL expression must return either a Boolean or an AuthorizationDecision");
}
catch (EvaluationException ex) {
throw new IllegalArgumentException("Failed to evaluate expression '" + expr.getExpressionString() + "'",
ex);
}
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation for specifying handling behavior when an authorization denied happens in
* method security or an
* {@link org.springframework.security.authorization.AuthorizationDeniedException} is
* thrown during method invocation
*
* @author Marcus da Coregio
* @since 6.3
* @see AuthorizationManagerAfterMethodInterceptor
* @see AuthorizationManagerBeforeMethodInterceptor
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HandleAuthorizationDenied {
/**
* The {@link MethodAuthorizationDeniedHandler} used to handle denied authorization
* results
* @return
*/
Class<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -44,7 +44,6 @@ import org.springframework.util.Assert;
*
* @author Evgeniy Cheban
* @author Josh Cummings
* @author DingHao
* @since 5.6
*/
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodInvocation> {
@@ -122,8 +121,7 @@ public final class Jsr250AuthorizationManager implements AuthorizationManager<Me
private Annotation findJsr250Annotation(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
Annotation annotation = findAnnotation(specificMethod);
return (annotation != null) ? annotation
: findAnnotation((targetClass != null) ? targetClass : specificMethod.getDeclaringClass());
return (annotation != null) ? annotation : findAnnotation(specificMethod.getDeclaringClass());
}
private Annotation findAnnotation(Method method) {

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.lang.Nullable;
import org.springframework.security.authorization.AuthorizationResult;
/**
* An interface used to define a strategy to handle denied method invocations
*
* @author Marcus da Coregio
* @since 6.3
* @see org.springframework.security.access.prepost.PreAuthorize
* @see org.springframework.security.access.prepost.PostAuthorize
*/
public interface MethodAuthorizationDeniedHandler {
/**
* Handle denied method invocations, implementations might either throw an
* {@link org.springframework.security.authorization.AuthorizationDeniedException} or
* a replacement result instead of invoking the method, e.g. a masked value.
* @param methodInvocation the {@link MethodInvocation} related to the authorization
* denied
* @param authorizationResult the authorization denied result
* @return a replacement result for the denied method invocation, or null, or a
* {@link reactor.core.publisher.Mono} for reactive applications
*/
@Nullable
Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult);
/**
* Handle denied method invocations, implementations might either throw an
* {@link org.springframework.security.authorization.AuthorizationDeniedException} or
* a replacement result instead of invoking the method, e.g. a masked value. By
* default, this method invokes
* {@link #handleDeniedInvocation(MethodInvocation, AuthorizationResult)}.
* @param methodInvocationResult the object containing the {@link MethodInvocation}
* and the result produced
* @param authorizationResult the authorization denied result
* @return a replacement result for the denied method invocation, or null, or a
* {@link reactor.core.publisher.Mono} for reactive applications
*/
@Nullable
default Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return handleDeniedInvocation(methodInvocationResult.getMethodInvocation(), authorizationResult);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -20,13 +20,13 @@ import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
import org.springframework.security.core.Authentication;
/**
@@ -37,8 +37,7 @@ import org.springframework.security.core.Authentication;
* @author Evgeniy Cheban
* @since 5.6
*/
public final class PostAuthorizeAuthorizationManager
implements AuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedHandler {
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
@@ -47,31 +46,7 @@ public final class PostAuthorizeAuthorizationManager
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Configure pre/post-authorization template resolution
* <p>
* By default, this value is <code>null</code>, which indicates that templates should
* not be resolved.
* @param defaults - whether to resolve pre/post-authorization templates parameters
* @since 6.3
*/
public void setTemplateDefaults(PrePostTemplateDefaults defaults) {
this.registry.setTemplateDefaults(defaults);
}
/**
* Invokes
* {@link PostAuthorizeExpressionAttributeRegistry#setApplicationContext(ApplicationContext)}
* with the provided {@link ApplicationContext}.
* @param context the {@link ApplicationContext}
* @since 6.3
* @see PreAuthorizeExpressionAttributeRegistry#setApplicationContext(ApplicationContext)
*/
public void setApplicationContext(ApplicationContext context) {
this.registry.setApplicationContext(context);
this.registry = new PostAuthorizeExpressionAttributeRegistry(expressionHandler);
}
/**
@@ -92,23 +67,8 @@ public final class PostAuthorizeAuthorizationManager
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());
expressionHandler.setReturnObject(mi.getResult(), ctx);
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
return postAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
return postAuthorizeAttribute.getHandler()
.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import org.springframework.expression.Expression;
import org.springframework.util.Assert;
/**
* An {@link ExpressionAttribute} that carries additional properties for
* {@code @PostAuthorize}.
*
* @author Marcus da Coregio
*/
class PostAuthorizeExpressionAttribute extends ExpressionAttribute {
private final MethodAuthorizationDeniedHandler handler;
PostAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedHandler handler) {
super(expression);
Assert.notNull(handler, "handler cannot be null");
this.handler = handler;
}
MethodAuthorizationDeniedHandler getHandler() {
return this.handler;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,16 +16,14 @@
package org.springframework.security.authorization.method;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Function;
import reactor.util.annotation.NonNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.util.Assert;
@@ -33,76 +31,42 @@ import org.springframework.util.Assert;
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @author DingHao
* @since 5.8
*/
final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
private final MethodSecurityExpressionHandler expressionHandler;
PostAuthorizeExpressionAttributeRegistry() {
this.handlerResolver = (clazz) -> this.defaultHandler;
this(new DefaultMethodSecurityExpressionHandler());
}
PostAuthorizeExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
}
MethodSecurityExpressionHandler getExpressionHandler() {
return this.expressionHandler;
}
@NonNull
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod, targetClass);
PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod);
if (postAuthorize == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value());
MethodAuthorizationDeniedHandler deniedHandler = resolveHandler(method, targetClass);
return new PostAuthorizeExpressionAttribute(expression, deniedHandler);
Expression postAuthorizeExpression = this.expressionHandler.getExpressionParser()
.parseExpression(postAuthorize.value());
return new ExpressionAttribute(postAuthorizeExpression);
}
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
Function<AnnotatedElement, HandleAuthorizationDenied> lookup = AuthorizationAnnotationUtils
.withDefaults(HandleAuthorizationDenied.class);
HandleAuthorizationDenied deniedHandler = lookup.apply(method);
if (deniedHandler != null) {
return this.handlerResolver.apply(deniedHandler.handlerClass());
}
deniedHandler = lookup.apply(targetClass(method, targetClass));
if (deniedHandler != null) {
return this.handlerResolver.apply(deniedHandler.handlerClass());
}
return this.defaultHandler;
}
private PostAuthorize findPostAuthorizeAnnotation(Method method, Class<?> targetClass) {
Function<AnnotatedElement, PostAuthorize> lookup = findUniqueAnnotation(PostAuthorize.class);
PostAuthorize postAuthorize = lookup.apply(method);
return (postAuthorize != null) ? postAuthorize : lookup.apply(targetClass(method, targetClass));
}
/**
* Uses the provided {@link ApplicationContext} to resolve the
* {@link MethodAuthorizationDeniedPostProcessor} from {@link PostAuthorize}
* @param context the {@link ApplicationContext} to use
*/
void setApplicationContext(ApplicationContext context) {
Assert.notNull(context, "context cannot be null");
this.handlerResolver = (clazz) -> resolveHandler(context, clazz);
}
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
Class<? extends MethodAuthorizationDeniedHandler> handlerClass) {
if (handlerClass == this.defaultHandler.getClass()) {
return this.defaultHandler;
}
String[] beanNames = context.getBeanNamesForType(handlerClass);
if (beanNames.length == 0) {
throw new IllegalStateException("Could not find a bean of type " + handlerClass.getName());
}
if (beanNames.length > 1) {
throw new IllegalStateException("Expected to find a single bean of type " + handlerClass.getName()
+ " but found " + Arrays.toString(beanNames));
}
return context.getBean(beanNames[0], handlerClass);
private PostAuthorize findPostAuthorizeAnnotation(Method method) {
PostAuthorize postAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostAuthorize.class);
return (postAuthorize != null) ? postAuthorize
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostAuthorize.class);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -19,12 +19,10 @@ package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@@ -38,9 +36,9 @@ import org.springframework.util.Assert;
* @since 5.8
*/
public final class PostAuthorizeReactiveAuthorizationManager
implements ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedHandler {
implements ReactiveAuthorizationManager<MethodInvocationResult> {
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
private final PostAuthorizeExpressionAttributeRegistry registry;
public PostAuthorizeReactiveAuthorizationManager() {
this(new DefaultMethodSecurityExpressionHandler());
@@ -48,23 +46,7 @@ public final class PostAuthorizeReactiveAuthorizationManager
public PostAuthorizeReactiveAuthorizationManager(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Configure pre/post-authorization template resolution
* <p>
* By default, this value is <code>null</code>, which indicates that templates should
* not be resolved.
* @param defaults - whether to resolve pre/post-authorization templates parameters
* @since 6.3
*/
public void setTemplateDefaults(PrePostTemplateDefaults defaults) {
this.registry.setTemplateDefaults(defaults);
}
public void setApplicationContext(ApplicationContext context) {
this.registry.setApplicationContext(context);
this.registry = new PostAuthorizeExpressionAttributeRegistry(expressionHandler);
}
/**
@@ -83,31 +65,14 @@ public final class PostAuthorizeReactiveAuthorizationManager
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return Mono.empty();
}
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
// @formatter:off
return authentication
.map((auth) -> expressionHandler.createEvaluationContext(auth, mi))
.doOnNext((ctx) -> expressionHandler.setReturnObject(result.getResult(), ctx))
.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
.cast(AuthorizationDecision.class);
.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
.map((granted) -> new ExpressionAttributeAuthorizationDecision(granted, attribute));
// @formatter:on
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
return postAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
return postAuthorizeAttribute.getHandler()
.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
}
}

View File

@@ -23,6 +23,9 @@ import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostFilter;
@@ -40,7 +43,8 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
* @author Josh Cummings
* @since 5.6
*/
public final class PostFilterAuthorizationMethodInterceptor implements AuthorizationAdvisor {
public final class PostFilterAuthorizationMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private Supplier<SecurityContextHolderStrategy> securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy;
@@ -63,19 +67,7 @@ public final class PostFilterAuthorizationMethodInterceptor implements Authoriza
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Configure pre/post-authorization template resolution
* <p>
* By default, this value is <code>null</code>, which indicates that templates should
* not be resolved.
* @param defaults - whether to resolve pre/post-authorization templates parameters
* @since 6.3
*/
public void setTemplateDefaults(PrePostTemplateDefaults defaults) {
this.registry.setTemplateDefaults(defaults);
this.registry = new PostFilterExpressionAttributeRegistry(expressionHandler);
}
/**

View File

@@ -26,6 +26,9 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.Ordered;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.expression.EvaluationContext;
@@ -43,9 +46,10 @@ import org.springframework.util.Assert;
* @author Evgeniy Cheban
* @since 5.8
*/
public final class PostFilterAuthorizationReactiveMethodInterceptor implements AuthorizationAdvisor {
public final class PostFilterAuthorizationReactiveMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private final PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry();
private final PostFilterExpressionAttributeRegistry registry;
private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class);
@@ -63,19 +67,7 @@ public final class PostFilterAuthorizationReactiveMethodInterceptor implements A
*/
public PostFilterAuthorizationReactiveMethodInterceptor(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Configure pre/post-authorization template resolution
* <p>
* By default, this value is <code>null</code>, which indicates that templates should
* not be resolved.
* @param defaults - whether to resolve pre/post-authorization templates parameters
* @since 6.3
*/
public void setTemplateDefaults(PrePostTemplateDefaults defaults) {
this.registry.setTemplateDefaults(defaults);
this.registry = new PostFilterExpressionAttributeRegistry(expressionHandler);
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,41 +16,56 @@
package org.springframework.security.authorization.method;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.function.Function;
import org.springframework.aop.support.AopUtils;
import org.springframework.expression.Expression;
import org.springframework.lang.NonNull;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.util.Assert;
/**
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @author DingHao
* @since 5.8
*/
final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
private final MethodSecurityExpressionHandler expressionHandler;
PostFilterExpressionAttributeRegistry() {
this.expressionHandler = new DefaultMethodSecurityExpressionHandler();
}
PostFilterExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
}
MethodSecurityExpressionHandler getExpressionHandler() {
return this.expressionHandler;
}
@NonNull
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PostFilter postFilter = findPostFilterAnnotation(specificMethod, targetClass);
PostFilter postFilter = findPostFilterAnnotation(specificMethod);
if (postFilter == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression postFilterExpression = getExpressionHandler().getExpressionParser()
Expression postFilterExpression = this.expressionHandler.getExpressionParser()
.parseExpression(postFilter.value());
return new ExpressionAttribute(postFilterExpression);
}
private PostFilter findPostFilterAnnotation(Method method, Class<?> targetClass) {
Function<AnnotatedElement, PostFilter> lookup = findUniqueAnnotation(PostFilter.class);
PostFilter postFilter = lookup.apply(method);
return (postFilter != null) ? postFilter : lookup.apply(targetClass(method, targetClass));
private PostFilter findPostFilterAnnotation(Method method) {
PostFilter postFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostFilter.class);
return (postFilter != null) ? postFilter
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostFilter.class);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -20,13 +20,13 @@ import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
import org.springframework.security.core.Authentication;
/**
@@ -37,8 +37,7 @@ import org.springframework.security.core.Authentication;
* @author Evgeniy Cheban
* @since 5.6
*/
public final class PreAuthorizeAuthorizationManager
implements AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
@@ -47,23 +46,7 @@ public final class PreAuthorizeAuthorizationManager
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Configure pre/post-authorization template resolution
* <p>
* By default, this value is <code>null</code>, which indicates that templates should
* not be resolved.
* @param defaults - whether to resolve pre/post-authorization templates parameters
* @since 6.3
*/
public void setTemplateDefaults(PrePostTemplateDefaults defaults) {
this.registry.setTemplateDefaults(defaults);
}
public void setApplicationContext(ApplicationContext context) {
this.registry.setApplicationContext(context);
this.registry = new PreAuthorizeExpressionAttributeRegistry(expressionHandler);
}
/**
@@ -82,14 +65,8 @@ public final class PreAuthorizeAuthorizationManager
return null;
}
EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
return preAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult);
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import org.springframework.expression.Expression;
import org.springframework.util.Assert;
/**
* An {@link ExpressionAttribute} that carries additional properties for
* {@code @PreAuthorize}.
*
* @author Marcus da Coregio
*/
class PreAuthorizeExpressionAttribute extends ExpressionAttribute {
private final MethodAuthorizationDeniedHandler handler;
PreAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedHandler handler) {
super(expression);
Assert.notNull(handler, "handler cannot be null");
this.handler = handler;
}
MethodAuthorizationDeniedHandler getHandler() {
return this.handler;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,16 +16,14 @@
package org.springframework.security.authorization.method;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Function;
import reactor.util.annotation.NonNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.Assert;
@@ -33,76 +31,46 @@ import org.springframework.util.Assert;
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @author DingHao
* @since 5.8
*/
final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
private final MethodSecurityExpressionHandler expressionHandler;
PreAuthorizeExpressionAttributeRegistry() {
this.handlerResolver = (clazz) -> this.defaultHandler;
this.expressionHandler = new DefaultMethodSecurityExpressionHandler();
}
PreAuthorizeExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
}
/**
* Returns the {@link MethodSecurityExpressionHandler}.
* @return the {@link MethodSecurityExpressionHandler} to use
*/
MethodSecurityExpressionHandler getExpressionHandler() {
return this.expressionHandler;
}
@NonNull
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod, targetClass);
PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);
if (preAuthorize == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(preAuthorize.value());
MethodAuthorizationDeniedHandler handler = resolveHandler(method, targetClass);
return new PreAuthorizeExpressionAttribute(expression, handler);
Expression preAuthorizeExpression = this.expressionHandler.getExpressionParser()
.parseExpression(preAuthorize.value());
return new ExpressionAttribute(preAuthorizeExpression);
}
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
Function<AnnotatedElement, HandleAuthorizationDenied> lookup = AuthorizationAnnotationUtils
.withDefaults(HandleAuthorizationDenied.class);
HandleAuthorizationDenied deniedHandler = lookup.apply(method);
if (deniedHandler != null) {
return this.handlerResolver.apply(deniedHandler.handlerClass());
}
deniedHandler = lookup.apply(targetClass(method, targetClass));
if (deniedHandler != null) {
return this.handlerResolver.apply(deniedHandler.handlerClass());
}
return this.defaultHandler;
}
private PreAuthorize findPreAuthorizeAnnotation(Method method, Class<?> targetClass) {
Function<AnnotatedElement, PreAuthorize> lookup = findUniqueAnnotation(PreAuthorize.class);
PreAuthorize preAuthorize = lookup.apply(method);
return (preAuthorize != null) ? preAuthorize : lookup.apply(targetClass(method, targetClass));
}
/**
* Uses the provided {@link ApplicationContext} to resolve the
* {@link MethodAuthorizationDeniedHandler} from {@link PreAuthorize}.
* @param context the {@link ApplicationContext} to use
*/
void setApplicationContext(ApplicationContext context) {
Assert.notNull(context, "context cannot be null");
this.handlerResolver = (clazz) -> resolveHandler(context, clazz);
}
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
Class<? extends MethodAuthorizationDeniedHandler> handlerClass) {
if (handlerClass == this.defaultHandler.getClass()) {
return this.defaultHandler;
}
String[] beanNames = context.getBeanNamesForType(handlerClass);
if (beanNames.length == 0) {
throw new IllegalStateException("Could not find a bean of type " + handlerClass.getName());
}
if (beanNames.length > 1) {
throw new IllegalStateException("Expected to find a single bean of type " + handlerClass.getName()
+ " but found " + Arrays.toString(beanNames));
}
return context.getBean(beanNames[0], handlerClass);
private PreAuthorize findPreAuthorizeAnnotation(Method method) {
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
return (preAuthorize != null) ? preAuthorize
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreAuthorize.class);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -19,12 +19,10 @@ package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@@ -37,10 +35,9 @@ import org.springframework.util.Assert;
* @author Evgeniy Cheban
* @since 5.8
*/
public final class PreAuthorizeReactiveAuthorizationManager
implements ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
private final PreAuthorizeExpressionAttributeRegistry registry;
public PreAuthorizeReactiveAuthorizationManager() {
this(new DefaultMethodSecurityExpressionHandler());
@@ -48,23 +45,7 @@ public final class PreAuthorizeReactiveAuthorizationManager
public PreAuthorizeReactiveAuthorizationManager(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Configure pre/post-authorization template resolution
* <p>
* By default, this value is <code>null</code>, which indicates that templates should
* not be resolved.
* @param defaults - whether to resolve pre/post-authorization templates parameters
* @since 6.3
*/
public void setTemplateDefaults(PrePostTemplateDefaults defaults) {
this.registry.setTemplateDefaults(defaults);
}
public void setApplicationContext(ApplicationContext context) {
this.registry.setApplicationContext(context);
this.registry = new PreAuthorizeExpressionAttributeRegistry(expressionHandler);
}
/**
@@ -84,16 +65,9 @@ public final class PreAuthorizeReactiveAuthorizationManager
// @formatter:off
return authentication
.map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi))
.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
.cast(AuthorizationDecision.class);
.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
.map((granted) -> new ExpressionAttributeAuthorizationDecision(granted, attribute));
// @formatter:on
}
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
return preAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult);
}
}

View File

@@ -23,6 +23,9 @@ import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreFilter;
@@ -41,7 +44,8 @@ import org.springframework.util.StringUtils;
* @author Josh Cummings
* @since 5.6
*/
public final class PreFilterAuthorizationMethodInterceptor implements AuthorizationAdvisor {
public final class PreFilterAuthorizationMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private Supplier<SecurityContextHolderStrategy> securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy;
@@ -64,19 +68,7 @@ public final class PreFilterAuthorizationMethodInterceptor implements Authorizat
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Configure pre/post-authorization template resolution
* <p>
* By default, this value is <code>null</code>, which indicates that templates should
* not be resolved.
* @param defaults - whether to resolve pre/post-authorization templates parameters
* @since 6.3
*/
public void setTemplateDefaults(PrePostTemplateDefaults defaults) {
this.registry.setTemplateDefaults(defaults);
this.registry = new PreFilterExpressionAttributeRegistry(expressionHandler);
}
/**

View File

@@ -26,7 +26,10 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
@@ -47,9 +50,10 @@ import org.springframework.util.StringUtils;
* @author Evgeniy Cheban
* @since 5.8
*/
public final class PreFilterAuthorizationReactiveMethodInterceptor implements AuthorizationAdvisor {
public final class PreFilterAuthorizationReactiveMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private final PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry();
private final PreFilterExpressionAttributeRegistry registry;
private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class);
@@ -66,19 +70,7 @@ public final class PreFilterAuthorizationReactiveMethodInterceptor implements Au
*/
public PreFilterAuthorizationReactiveMethodInterceptor(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.registry.setExpressionHandler(expressionHandler);
}
/**
* Configure pre/post-authorization template resolution
* <p>
* By default, this value is <code>null</code>, which indicates that templates should
* not be resolved.
* @param defaults - whether to resolve pre/post-authorization templates parameters
* @since 6.3
*/
public void setTemplateDefaults(PrePostTemplateDefaults defaults) {
this.registry.setTemplateDefaults(defaults);
this.registry = new PreFilterExpressionAttributeRegistry(expressionHandler);
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,42 +16,57 @@
package org.springframework.security.authorization.method;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.function.Function;
import org.springframework.aop.support.AopUtils;
import org.springframework.expression.Expression;
import org.springframework.lang.NonNull;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.util.Assert;
/**
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @author DingHao
* @since 5.8
*/
final class PreFilterExpressionAttributeRegistry
extends AbstractExpressionAttributeRegistry<PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute> {
private final MethodSecurityExpressionHandler expressionHandler;
PreFilterExpressionAttributeRegistry() {
this.expressionHandler = new DefaultMethodSecurityExpressionHandler();
}
PreFilterExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
}
MethodSecurityExpressionHandler getExpressionHandler() {
return this.expressionHandler;
}
@NonNull
@Override
PreFilterExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PreFilter preFilter = findPreFilterAnnotation(specificMethod, targetClass);
PreFilter preFilter = findPreFilterAnnotation(specificMethod);
if (preFilter == null) {
return PreFilterExpressionAttribute.NULL_ATTRIBUTE;
}
Expression preFilterExpression = getExpressionHandler().getExpressionParser()
Expression preFilterExpression = this.expressionHandler.getExpressionParser()
.parseExpression(preFilter.value());
return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget());
}
private PreFilter findPreFilterAnnotation(Method method, Class<?> targetClass) {
Function<AnnotatedElement, PreFilter> lookup = findUniqueAnnotation(PreFilter.class);
PreFilter preFilter = lookup.apply(method);
return (preFilter != null) ? preFilter : lookup.apply(targetClass(method, targetClass));
private PreFilter findPreFilterAnnotation(Method method) {
PreFilter preFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreFilter.class);
return (preFilter != null) ? preFilter
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreFilter.class);
}
static final class PreFilterExpressionAttribute extends ExpressionAttribute {

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2002-2024 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.method;
/**
* A component for configuring various cross-cutting aspects of pre/post method security
*
* @author Josh Cummings
* @since 6.3
* @see org.springframework.security.access.prepost.PreAuthorize
* @see org.springframework.security.access.prepost.PostAuthorize
* @see org.springframework.security.access.prepost.PreFilter
* @see org.springframework.security.access.prepost.PostFilter
*/
public final class PrePostTemplateDefaults {
private boolean ignoreUnknown = true;
/**
* Whether template resolution should ignore placeholders it doesn't recognize.
* <p>
* By default, this value is <code>true</code>.
* @since 6.3
*/
public boolean isIgnoreUnknown() {
return this.ignoreUnknown;
}
/**
* Configure template resolution to ignore unknown placeholders. When set to
* <code>false</code>, template resolution will throw an exception for unknown
* placeholders.
* <p>
* By default, this value is <code>true</code>.
* @param ignoreUnknown - whether to ignore unknown placeholders parameters
* @since 6.3
*/
public void setIgnoreUnknown(boolean ignoreUnknown) {
this.ignoreUnknown = ignoreUnknown;
}
}

View File

@@ -21,8 +21,6 @@ import reactor.core.publisher.Mono;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
/**
* For internal use only, as this contract is likely to change.
@@ -32,33 +30,6 @@ import org.springframework.security.authorization.ExpressionAuthorizationDecisio
*/
final class ReactiveExpressionUtils {
static Mono<AuthorizationResult> evaluate(Expression expr, EvaluationContext ctx) {
return Mono.defer(() -> {
Object value;
try {
value = expr.getValue(ctx);
}
catch (EvaluationException ex) {
return Mono.error(() -> new IllegalArgumentException(
"Failed to evaluate expression '" + expr.getExpressionString() + "'", ex));
}
if (value instanceof Mono<?> mono) {
return mono.flatMap((data) -> adapt(expr, data));
}
return adapt(expr, value);
});
}
private static Mono<AuthorizationResult> adapt(Expression expr, Object value) {
if (value instanceof Boolean granted) {
return Mono.just(new ExpressionAuthorizationDecision(granted, expr));
}
if (value instanceof AuthorizationResult decision) {
return Mono.just(decision);
}
return createInvalidReturnTypeMono(expr);
}
static Mono<Boolean> evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
return Mono.defer(() -> {
Object value;
@@ -85,9 +56,9 @@ final class ReactiveExpressionUtils {
});
}
private static <T> Mono<T> createInvalidReturnTypeMono(Expression expr) {
return Mono.error(() -> new IllegalStateException("Expression: '" + expr.getExpressionString()
+ "' must return boolean, Mono<Boolean>, AuthorizationResult, or Mono<AuthorizationResult>"));
private static Mono<Boolean> createInvalidReturnTypeMono(Expression expr) {
return Mono.error(() -> new IllegalStateException(
"Expression: '" + expr.getExpressionString() + "' must return boolean or Mono<Boolean>"));
}
private ReactiveExpressionUtils() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -41,7 +41,6 @@ import org.springframework.util.Assert;
* contains a specified authority from the Spring Security's {@link Secured} annotation.
*
* @author Evgeniy Cheban
* @author DingHao
* @since 5.6
*/
public final class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
@@ -87,14 +86,14 @@ public final class SecuredAuthorizationManager implements AuthorizationManager<M
private Set<String> resolveAuthorities(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
Secured secured = findSecuredAnnotation(specificMethod, targetClass);
Secured secured = findSecuredAnnotation(specificMethod);
return (secured != null) ? Set.of(secured.value()) : Collections.emptySet();
}
private Secured findSecuredAnnotation(Method method, Class<?> targetClass) {
private Secured findSecuredAnnotation(Method method) {
Secured secured = AuthorizationAnnotationUtils.findUniqueAnnotation(method, Secured.class);
return (secured != null) ? secured : AuthorizationAnnotationUtils
.findUniqueAnnotation((targetClass != null) ? targetClass : method.getDeclaringClass(), Secured.class);
return (secured != null) ? secured
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), Secured.class);
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
/**
* An implementation of {@link MethodAuthorizationDeniedHandler} that throws
* {@link AuthorizationDeniedException}
*
* @author Marcus da Coregio
* @since 6.3
*/
public final class ThrowingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
if (authorizationResult instanceof AuthorizationDeniedException denied) {
throw denied;
}
throw new AuthorizationDeniedException("Access Denied", authorizationResult);
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
if (authorizationResult instanceof AuthorizationDeniedException denied) {
throw denied;
}
throw new AuthorizationDeniedException("Access Denied", authorizationResult);
}
}

View File

@@ -66,9 +66,9 @@ import java.util.Properties;
*/
class ComparableVersion implements Comparable<ComparableVersion> {
private static final int MAX_INT_ITEM_LENGTH = 9;
private static final int MAX_INTITEM_LENGTH = 9;
private static final int MAX_LONG_ITEM_LENGTH = 18;
private static final int MAX_LONGITEM_LENGTH = 18;
private String value;
@@ -559,11 +559,11 @@ class ComparableVersion implements Comparable<ComparableVersion> {
private static Item parseItem(boolean isDigit, String buf) {
if (isDigit) {
buf = stripLeadingZeroes(buf);
if (buf.length() <= MAX_INT_ITEM_LENGTH) {
if (buf.length() <= MAX_INTITEM_LENGTH) {
// lower than 2^31
return new IntItem(buf);
}
else if (buf.length() <= MAX_LONG_ITEM_LENGTH) {
else if (buf.length() <= MAX_LONGITEM_LENGTH) {
// lower than 2^63
return new LongItem(buf);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2017 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.
@@ -39,6 +39,9 @@ public final class SpringSecurityCoreVersion {
/**
* Global Serialization value for Spring Security classes.
*
* N.B. Classes are not intended to be serializable between different versions. See
* SEC-1709 for why we still need a serial version.
*/
public static final long SERIAL_VERSION_UID = 620L;

View File

@@ -1,94 +0,0 @@
/*
* Copyright 2002-2024 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.core.session;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Provides an in-memory implementation of {@link ReactiveSessionRegistry}.
*
* @author Marcus da Coregio
* @since 6.3
*/
public class InMemoryReactiveSessionRegistry implements ReactiveSessionRegistry {
private final ConcurrentMap<Object, Set<String>> sessionIdsByPrincipal;
private final Map<String, ReactiveSessionInformation> sessionById;
public InMemoryReactiveSessionRegistry() {
this.sessionIdsByPrincipal = new ConcurrentHashMap<>();
this.sessionById = new ConcurrentHashMap<>();
}
public InMemoryReactiveSessionRegistry(ConcurrentMap<Object, Set<String>> sessionIdsByPrincipal,
Map<String, ReactiveSessionInformation> sessionById) {
this.sessionIdsByPrincipal = sessionIdsByPrincipal;
this.sessionById = sessionById;
}
@Override
public Flux<ReactiveSessionInformation> getAllSessions(Object principal) {
return Flux.fromIterable(this.sessionIdsByPrincipal.getOrDefault(principal, Collections.emptySet()))
.map(this.sessionById::get);
}
@Override
public Mono<Void> saveSessionInformation(ReactiveSessionInformation information) {
this.sessionById.put(information.getSessionId(), information);
this.sessionIdsByPrincipal.computeIfAbsent(information.getPrincipal(), (key) -> new CopyOnWriteArraySet<>())
.add(information.getSessionId());
return Mono.empty();
}
@Override
public Mono<ReactiveSessionInformation> getSessionInformation(String sessionId) {
return Mono.justOrEmpty(this.sessionById.get(sessionId));
}
@Override
public Mono<ReactiveSessionInformation> removeSessionInformation(String sessionId) {
return getSessionInformation(sessionId).doOnNext((sessionInformation) -> {
this.sessionById.remove(sessionId);
Set<String> sessionsUsedByPrincipal = this.sessionIdsByPrincipal.get(sessionInformation.getPrincipal());
if (sessionsUsedByPrincipal != null) {
sessionsUsedByPrincipal.remove(sessionId);
if (sessionsUsedByPrincipal.isEmpty()) {
this.sessionIdsByPrincipal.remove(sessionInformation.getPrincipal());
}
}
});
}
@Override
public Mono<ReactiveSessionInformation> updateLastAccessTime(String sessionId) {
ReactiveSessionInformation session = this.sessionById.get(sessionId);
if (session != null) {
return session.refreshLastRequest().thenReturn(session);
}
return Mono.empty();
}
}

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2002-2023 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.core.session;
import java.io.Serial;
import java.io.Serializable;
import java.time.Instant;
import reactor.core.publisher.Mono;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.util.Assert;
public class ReactiveSessionInformation implements Serializable {
@Serial
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private Instant lastAccessTime;
private final Object principal;
private final String sessionId;
private boolean expired = false;
public ReactiveSessionInformation(Object principal, String sessionId, Instant lastAccessTime) {
Assert.notNull(principal, "principal cannot be null");
Assert.hasText(sessionId, "sessionId cannot be null");
Assert.notNull(lastAccessTime, "lastAccessTime cannot be null");
this.principal = principal;
this.sessionId = sessionId;
this.lastAccessTime = lastAccessTime;
}
public ReactiveSessionInformation withSessionId(String sessionId) {
return new ReactiveSessionInformation(this.principal, sessionId, this.lastAccessTime);
}
public Mono<Void> invalidate() {
return Mono.fromRunnable(() -> this.expired = true);
}
public Mono<Void> refreshLastRequest() {
this.lastAccessTime = Instant.now();
return Mono.empty();
}
public Instant getLastAccessTime() {
return this.lastAccessTime;
}
public Object getPrincipal() {
return this.principal;
}
public String getSessionId() {
return this.sessionId;
}
public boolean isExpired() {
return this.expired;
}
public void setLastAccessTime(Instant lastAccessTime) {
this.lastAccessTime = lastAccessTime;
}
}

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2002-2024 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.core.session;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Maintains a registry of {@link ReactiveSessionInformation} instances.
*
* @author Marcus da Coregio
* @since 6.3
*/
public interface ReactiveSessionRegistry {
/**
* Gets all the known {@link ReactiveSessionInformation} instances for the specified
* principal.
* @param principal the principal
* @return the {@link ReactiveSessionInformation} instances associated with the
* principal
*/
Flux<ReactiveSessionInformation> getAllSessions(Object principal);
/**
* Saves the {@link ReactiveSessionInformation}
* @param information the {@link ReactiveSessionInformation} to save
* @return a {@link Mono} that completes when the session is saved
*/
Mono<Void> saveSessionInformation(ReactiveSessionInformation information);
/**
* Gets the {@link ReactiveSessionInformation} for the specified session identifier.
* @param sessionId the session identifier
* @return the {@link ReactiveSessionInformation} for the session.
*/
Mono<ReactiveSessionInformation> getSessionInformation(String sessionId);
/**
* Removes the specified session from the registry.
* @param sessionId the session identifier
* @return a {@link Mono} that completes when the session is removed
*/
Mono<ReactiveSessionInformation> removeSessionInformation(String sessionId);
/**
* Updates the last accessed time of the {@link ReactiveSessionInformation}
* @param sessionId the session identifier
* @return a {@link Mono} that completes when the session is updated
*/
Mono<ReactiveSessionInformation> updateLastAccessTime(String sessionId);
}

View File

@@ -67,18 +67,14 @@ public interface UserDetails extends Serializable {
* @return <code>true</code> if the user's account is valid (ie non-expired),
* <code>false</code> if no longer valid (ie expired)
*/
default boolean isAccountNonExpired() {
return true;
}
boolean isAccountNonExpired();
/**
* Indicates whether the user is locked or unlocked. A locked user cannot be
* authenticated.
* @return <code>true</code> if the user is not locked, <code>false</code> otherwise
*/
default boolean isAccountNonLocked() {
return true;
}
boolean isAccountNonLocked();
/**
* Indicates whether the user's credentials (password) has expired. Expired
@@ -86,17 +82,13 @@ public interface UserDetails extends Serializable {
* @return <code>true</code> if the user's credentials are valid (ie non-expired),
* <code>false</code> if no longer valid (ie expired)
*/
default boolean isCredentialsNonExpired() {
return true;
}
boolean isCredentialsNonExpired();
/**
* Indicates whether the user is enabled or disabled. A disabled user cannot be
* authenticated.
* @return <code>true</code> if the user is enabled, <code>false</code> otherwise
*/
default boolean isEnabled() {
return true;
}
boolean isEnabled();
}

View File

@@ -28,11 +28,11 @@ DigestAuthenticationFilter.nonceExpired=El nonce ha expirat/ha arribat fora de t
DigestAuthenticationFilter.nonceNotNumeric=El nonce token haur\u00eda d'haver produ\u00eft un token num\u00e8ric inicial, per\u00f2 era {0}
DigestAuthenticationFilter.nonceNotTwoTokens=El nonce hauria de produ\u00efr dos tokens, i no {0}
DigestAuthenticationFilter.usernameNotFound=No s'ha trobat el nom d'usuari {0}
ExceptionTranslationFilter.insufficientAuthentication=Per accedir a aquest recurs cal autenticaci\u00f3 completa
#ExceptionTranslationFilter.insufficientAuthentication=Full authentication is required to access this resource
JdbcDaoImpl.noAuthority=L'usuari {0} no t\u00e9 GrantedAuthority
JdbcDaoImpl.notFound=No s'ha trobat l'usuari {0}
LdapAuthenticationProvider.badCredentials=Credencials err\u00f2nies
LdapAuthenticationProvider.badLdapConnection=Ha fallat la connexi\u00f3 al servidor LDAP
#LdapAuthenticationProvider.badLdapConnection=Connection to LDAP server failed
LdapAuthenticationProvider.credentialsExpired=Les credencials d'usuari han expirat
LdapAuthenticationProvider.disabled=L'usuari est\u00e0 deshabilitat
LdapAuthenticationProvider.expired=El compte d'usuari ha expirat

View File

@@ -32,7 +32,7 @@ ExceptionTranslationFilter.insufficientAuthentication=Para acceder a este recurs
JdbcDaoImpl.noAuthority=Usuario {0} no tiene GrantedAuthority
JdbcDaoImpl.notFound=Usuario {0} no encontrado
LdapAuthenticationProvider.badCredentials=Credenciales err\u00F3neas
LdapAuthenticationProvider.badLdapConnection=Fall\u00F3 la conexi\u00F3n al servidor LDAP
#LdapAuthenticationProvider.badLdapConnection=Connection to LDAP server failed
LdapAuthenticationProvider.credentialsExpired=Las credenciales del usuario han expirado
LdapAuthenticationProvider.disabled=El usuario est\u00E1 deshabilitado
LdapAuthenticationProvider.expired=La cuenta del usuario ha expirado

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2021 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.
@@ -25,7 +25,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('USER')")
@RolesAllowed("USER")
@RolesAllowed("ADMIN")
@Secured("USER")
public @interface RequireUserRole {

View File

@@ -26,7 +26,6 @@ import org.springframework.security.core.authority.AuthorityUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
@@ -206,99 +205,4 @@ public class RoleHierarchyImplTests {
.containsExactlyInAnyOrderElementsOf(allAuthorities);
}
@Test
public void testFromHierarchyWithTextBlock() {
RoleHierarchyImpl roleHierarchyImpl = RoleHierarchyImpl.fromHierarchy("""
ROLE_A > ROLE_B
ROLE_B > ROLE_C
ROLE_B > ROLE_D
""");
List<GrantedAuthority> flatAuthorities = AuthorityUtils.createAuthorityList("ROLE_A");
List<GrantedAuthority> allAuthorities = AuthorityUtils.createAuthorityList("ROLE_A", "ROLE_B", "ROLE_C",
"ROLE_D");
assertThat(roleHierarchyImpl).isNotNull();
assertThat(roleHierarchyImpl.getReachableGrantedAuthorities(flatAuthorities))
.containsExactlyInAnyOrderElementsOf(allAuthorities);
}
@Test
public void testFromHierarchyNoCycles() {
assertThatNoException().isThrownBy(() -> RoleHierarchyImpl
.fromHierarchy("ROLE_A > ROLE_B\nROLE_A > ROLE_C\nROLE_C > ROLE_D\nROLE_B > ROLE_D"));
}
@Test
public void testFromHierarchyCycles() {
assertThatExceptionOfType(CycleInRoleHierarchyException.class)
.isThrownBy(() -> RoleHierarchyImpl.fromHierarchy("ROLE_A > ROLE_A"));
assertThatExceptionOfType(CycleInRoleHierarchyException.class)
.isThrownBy(() -> RoleHierarchyImpl.fromHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_A"));
assertThatExceptionOfType(CycleInRoleHierarchyException.class)
.isThrownBy(() -> RoleHierarchyImpl.fromHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_C\nROLE_C > ROLE_A"));
assertThatExceptionOfType(CycleInRoleHierarchyException.class).isThrownBy(() -> RoleHierarchyImpl
.fromHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_C\nROLE_C > ROLE_E\nROLE_E > ROLE_D\nROLE_D > ROLE_B"));
assertThatExceptionOfType(CycleInRoleHierarchyException.class)
.isThrownBy(() -> RoleHierarchyImpl.fromHierarchy("ROLE_C > ROLE_B\nROLE_B > ROLE_A\nROLE_A > ROLE_B"));
}
@Test
public void testBuilderWithDefaultRolePrefix() {
RoleHierarchyImpl roleHierarchyImpl = RoleHierarchyImpl.withDefaultRolePrefix()
.role("A")
.implies("B")
.role("B")
.implies("C", "D")
.build();
List<GrantedAuthority> flatAuthorities = AuthorityUtils.createAuthorityList("ROLE_A");
List<GrantedAuthority> allAuthorities = AuthorityUtils.createAuthorityList("ROLE_A", "ROLE_B", "ROLE_C",
"ROLE_D");
assertThat(roleHierarchyImpl).isNotNull();
assertThat(roleHierarchyImpl.getReachableGrantedAuthorities(flatAuthorities))
.containsExactlyInAnyOrderElementsOf(allAuthorities);
}
@Test
public void testBuilderWithRolePrefix() {
RoleHierarchyImpl roleHierarchyImpl = RoleHierarchyImpl.withRolePrefix("CUSTOM_PREFIX_")
.role("A")
.implies("B")
.build();
List<GrantedAuthority> flatAuthorities = AuthorityUtils.createAuthorityList("CUSTOM_PREFIX_A");
List<GrantedAuthority> allAuthorities = AuthorityUtils.createAuthorityList("CUSTOM_PREFIX_A",
"CUSTOM_PREFIX_B");
assertThat(roleHierarchyImpl).isNotNull();
assertThat(roleHierarchyImpl.getReachableGrantedAuthorities(flatAuthorities))
.containsExactlyInAnyOrderElementsOf(allAuthorities);
}
@Test
public void testBuilderThrowIllegalArgumentExceptionWhenPrefixRoleNull() {
assertThatIllegalArgumentException().isThrownBy(() -> RoleHierarchyImpl.withRolePrefix(null));
}
@Test
public void testBuilderThrowIllegalArgumentExceptionWhenRoleEmpty() {
assertThatIllegalArgumentException().isThrownBy(() -> RoleHierarchyImpl.withDefaultRolePrefix().role(""));
}
@Test
public void testBuilderThrowIllegalArgumentExceptionWhenRoleNull() {
assertThatIllegalArgumentException().isThrownBy(() -> RoleHierarchyImpl.withDefaultRolePrefix().role(null));
}
@Test
public void testBuilderThrowIllegalArgumentExceptionWhenImpliedRolesNull() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RoleHierarchyImpl.withDefaultRolePrefix().role("A").implies((String) null));
}
@Test
public void testBuilderThrowIllegalArgumentExceptionWhenImpliedRolesEmpty() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RoleHierarchyImpl.withDefaultRolePrefix().role("A").implies());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2018 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.
@@ -77,43 +77,4 @@ public class DelegatingReactiveAuthenticationManagerTests {
.verify();
}
@Test
public void authenticateWhenContinueOnErrorAndFirstBadCredentialsThenTriesSecond() {
given(this.delegate1.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test")));
given(this.delegate2.authenticate(any())).willReturn(Mono.just(this.authentication));
DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError();
assertThat(manager.authenticate(this.authentication).block()).isEqualTo(this.authentication);
}
@Test
public void authenticateWhenContinueOnErrorAndBothDelegatesBadCredentialsThenError() {
given(this.delegate1.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test")));
given(this.delegate2.authenticate(any())).willReturn(Mono.error(new BadCredentialsException("Test")));
DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError();
StepVerifier.create(manager.authenticate(this.authentication))
.expectError(BadCredentialsException.class)
.verify();
}
@Test
public void authenticateWhenContinueOnErrorAndDelegate1NotEmptyThenReturnsNotEmpty() {
given(this.delegate1.authenticate(any())).willReturn(Mono.just(this.authentication));
DelegatingReactiveAuthenticationManager manager = managerWithContinueOnError();
assertThat(manager.authenticate(this.authentication).block()).isEqualTo(this.authentication);
}
private DelegatingReactiveAuthenticationManager managerWithContinueOnError() {
DelegatingReactiveAuthenticationManager manager = new DelegatingReactiveAuthenticationManager(this.delegate1,
this.delegate2);
manager.setContinueOnError(true);
return manager;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -16,12 +16,9 @@
package org.springframework.security.authentication;
import java.util.function.Consumer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
/**
@@ -38,20 +35,14 @@ public class TestAuthentication extends PasswordEncodedUser {
AuthorityUtils.createAuthorityList("ROLE_USER"));
public static Authentication authenticatedAdmin() {
return authenticated(admin());
return autheticated(admin());
}
public static Authentication authenticatedUser() {
return authenticated(user());
return autheticated(user());
}
public static Authentication authenticatedUser(Consumer<User.UserBuilder> consumer) {
User.UserBuilder builder = withUsername("user");
consumer.accept(builder);
return authenticated(builder.build());
}
public static Authentication authenticated(UserDetails user) {
public static Authentication autheticated(UserDetails user) {
return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities());
}

View File

@@ -24,12 +24,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import reactor.test.StepVerifier;
import org.springframework.context.MessageSource;
import org.springframework.security.authentication.password.CompromisedPasswordCheckResult;
import org.springframework.security.authentication.password.CompromisedPasswordException;
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
@@ -38,7 +34,6 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.crypto.password.PasswordEncoder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
@@ -224,41 +219,6 @@ public class UserDetailsRepositoryReactiveAuthenticationManagerTests {
assertThatExceptionOfType(DisabledException.class).isThrownBy(() -> this.manager.authenticate(token).block());
}
@Test
public void authenticateWhenPasswordCompromisedThenException() {
// @formatter:off
UserDetails user = User.withUsername("user")
.password("{noop}password")
.roles("USER")
.build();
// @formatter:on
given(this.userDetailsService.findByUsername(any())).willReturn(Mono.just(user));
this.manager.setCompromisedPasswordChecker(new TestReactivePasswordChecker());
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(user,
"password");
StepVerifier.create(this.manager.authenticate(token))
.expectErrorSatisfies((ex) -> assertThat(ex).isInstanceOf(CompromisedPasswordException.class)
.withFailMessage("The provided password is compromised, please change your password"))
.verify();
}
@Test
public void authenticateWhenPasswordNotCompromisedThenSuccess() {
// @formatter:off
UserDetails user = User.withUsername("user")
.password("{noop}notcompromised")
.roles("USER")
.build();
// @formatter:on
given(this.userDetailsService.findByUsername(any())).willReturn(Mono.just(user));
this.manager.setCompromisedPasswordChecker(new TestReactivePasswordChecker());
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(user,
"notcompromised");
StepVerifier.create(this.manager.authenticate(token))
.assertNext((authentication) -> assertThat(authentication.getPrincipal()).isEqualTo(user))
.verifyComplete();
}
@Test
public void setMessageSourceWhenNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.manager.setMessageSource(null));
@@ -273,16 +233,4 @@ public class UserDetailsRepositoryReactiveAuthenticationManagerTests {
verify(source).getMessage(eq(code), any(), any());
}
static class TestReactivePasswordChecker implements ReactiveCompromisedPasswordChecker {
@Override
public Mono<CompromisedPasswordCheckResult> check(String password) {
if ("password".equals(password)) {
return Mono.just(new CompromisedPasswordCheckResult(true));
}
return Mono.just(new CompromisedPasswordCheckResult(false));
}
}
}

View File

@@ -33,9 +33,6 @@ import org.springframework.security.authentication.InternalAuthenticationService
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.password.CompromisedPasswordCheckResult;
import org.springframework.security.authentication.password.CompromisedPasswordChecker;
import org.springframework.security.authentication.password.CompromisedPasswordException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -51,7 +48,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -508,42 +504,6 @@ public class DaoAuthenticationProviderTests {
verify(encoder, times(0)).matches(anyString(), anyString());
}
@Test
void authenticateWhenPasswordLeakedThenException() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
provider.setUserDetailsService(withUsers(user));
provider.setCompromisedPasswordChecker(new TestCompromisedPasswordChecker());
assertThatExceptionOfType(CompromisedPasswordException.class).isThrownBy(
() -> provider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("user", "password")))
.withMessage("The provided password is compromised, please change your password");
}
@Test
void authenticateWhenPasswordNotLeakedThenNoException() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("strongpassword")
.roles("USER")
.build();
provider.setUserDetailsService(withUsers(user));
provider.setCompromisedPasswordChecker(new TestCompromisedPasswordChecker());
Authentication authentication = provider
.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("user", "strongpassword"));
assertThat(authentication).isNotNull();
}
private UserDetailsService withUsers(UserDetails... users) {
return new InMemoryUserDetailsManager(users);
}
private DaoAuthenticationProvider createProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance());
@@ -634,16 +594,4 @@ public class DaoAuthenticationProviderTests {
}
private static class TestCompromisedPasswordChecker implements CompromisedPasswordChecker {
@Override
public CompromisedPasswordCheckResult check(String password) {
if ("password".equals(password)) {
return new CompromisedPasswordCheckResult(true);
}
return new CompromisedPasswordCheckResult(false);
}
}
}

View File

@@ -1,422 +0,0 @@
/*
* Copyright 2002-2024 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.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.springframework.aop.Pointcut;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class AuthorizationAdvisorProxyFactoryTests {
private final Authentication user = TestAuthentication.authenticatedUser();
private final Authentication admin = TestAuthentication.authenticatedAdmin();
private final Flight flight = new Flight();
private final User alan = new User("alan", "alan", "turing");
@Test
public void proxyWhenPreAuthorizeThenHonors() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Flight flight = new Flight();
assertThat(flight.getAltitude()).isEqualTo(35000d);
Flight secured = proxy(factory, flight);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured::getAltitude);
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenPreAuthorizeOnInterfaceThenHonors() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
assertThat(this.alan.getFirstName()).isEqualTo("alan");
User secured = proxy(factory, this.alan);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured::getFirstName);
SecurityContextHolder.getContext().setAuthentication(authenticated("alan"));
assertThat(secured.getFirstName()).isEqualTo("alan");
SecurityContextHolder.getContext().setAuthentication(this.admin);
assertThat(secured.getFirstName()).isEqualTo("alan");
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenPreAuthorizeOnRecordThenHonors() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
HasSecret repo = new Repository("secret");
assertThat(repo.secret()).isEqualTo("secret");
HasSecret secured = proxy(factory, repo);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured::secret);
SecurityContextHolder.getContext().setAuthentication(this.user);
assertThat(repo.secret()).isEqualTo("secret");
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenImmutableListThenReturnsSecuredImmutableList() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
List<Flight> flights = List.of(this.flight);
List<Flight> secured = proxy(factory, flights);
secured.forEach(
(flight) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude));
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(secured::clear);
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenImmutableSetThenReturnsSecuredImmutableSet() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Set<Flight> flights = Set.of(this.flight);
Set<Flight> secured = proxy(factory, flights);
secured.forEach(
(flight) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude));
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(secured::clear);
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenQueueThenReturnsSecuredQueue() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Queue<Flight> flights = new LinkedList<>(List.of(this.flight));
Queue<Flight> secured = proxy(factory, flights);
assertThat(flights.size()).isEqualTo(secured.size());
secured.forEach(
(flight) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude));
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenImmutableSortedSetThenReturnsSecuredImmutableSortedSet() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
SortedSet<User> users = Collections.unmodifiableSortedSet(new TreeSet<>(Set.of(this.alan)));
SortedSet<User> secured = proxy(factory, users);
secured
.forEach((user) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(user::getFirstName));
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(secured::clear);
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenImmutableSortedMapThenReturnsSecuredImmutableSortedMap() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
SortedMap<String, User> users = Collections
.unmodifiableSortedMap(new TreeMap<>(Map.of(this.alan.getId(), this.alan)));
SortedMap<String, User> secured = proxy(factory, users);
secured.forEach(
(id, user) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(user::getFirstName));
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(secured::clear);
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenImmutableMapThenReturnsSecuredImmutableMap() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Map<String, User> users = Map.of(this.alan.getId(), this.alan);
Map<String, User> secured = proxy(factory, users);
secured.forEach(
(id, user) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(user::getFirstName));
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(secured::clear);
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenMutableListThenReturnsSecuredMutableList() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
List<Flight> flights = new ArrayList<>(List.of(this.flight));
List<Flight> secured = proxy(factory, flights);
secured.forEach(
(flight) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude));
secured.clear();
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenMutableSetThenReturnsSecuredMutableSet() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Set<Flight> flights = new HashSet<>(Set.of(this.flight));
Set<Flight> secured = proxy(factory, flights);
secured.forEach(
(flight) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude));
secured.clear();
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenMutableSortedSetThenReturnsSecuredMutableSortedSet() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
SortedSet<User> users = new TreeSet<>(Set.of(this.alan));
SortedSet<User> secured = proxy(factory, users);
secured.forEach((u) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(u::getFirstName));
secured.clear();
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenMutableSortedMapThenReturnsSecuredMutableSortedMap() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
SortedMap<String, User> users = new TreeMap<>(Map.of(this.alan.getId(), this.alan));
SortedMap<String, User> secured = proxy(factory, users);
secured.forEach((id, u) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(u::getFirstName));
secured.clear();
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenMutableMapThenReturnsSecuredMutableMap() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Map<String, User> users = new HashMap<>(Map.of(this.alan.getId(), this.alan));
Map<String, User> secured = proxy(factory, users);
secured.forEach((id, u) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(u::getFirstName));
secured.clear();
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenPreAuthorizeForOptionalThenHonors() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Optional<Flight> flights = Optional.of(this.flight);
assertThat(flights.get().getAltitude()).isEqualTo(35000d);
Optional<Flight> secured = proxy(factory, flights);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.ifPresent(Flight::getAltitude));
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenPreAuthorizeForSupplierThenHonors() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Supplier<Flight> flights = () -> this.flight;
assertThat(flights.get().getAltitude()).isEqualTo(35000d);
Supplier<Flight> secured = proxy(factory, flights);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.get().getAltitude());
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenPreAuthorizeForStreamThenHonors() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Stream<Flight> flights = Stream.of(this.flight);
Stream<Flight> secured = proxy(factory, flights);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.forEach(Flight::getAltitude));
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenPreAuthorizeForArrayThenHonors() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Flight[] flights = { this.flight };
Flight[] secured = proxy(factory, flights);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured[0]::getAltitude);
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenPreAuthorizeForIteratorThenHonors() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Iterator<Flight> flights = List.of(this.flight).iterator();
Iterator<Flight> secured = proxy(factory, flights);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.next().getAltitude());
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenPreAuthorizeForIterableThenHonors() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Iterable<User> users = new UserRepository();
Iterable<User> secured = proxy(factory, users);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.forEach(User::getFirstName));
SecurityContextHolder.clearContext();
}
@Test
public void proxyWhenPreAuthorizeForClassThenHonors() {
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
Class<Flight> clazz = proxy(factory, Flight.class);
assertThat(clazz.getSimpleName()).contains("SpringCGLIB$$");
Flight secured = proxy(factory, this.flight);
assertThat(secured.getClass()).isSameAs(clazz);
SecurityContextHolder.getContext().setAuthentication(this.user);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured::getAltitude);
SecurityContextHolder.clearContext();
}
@Test
public void setAdvisorsWhenProxyThenVisits() {
AuthorizationAdvisor advisor = mock(AuthorizationAdvisor.class);
given(advisor.getAdvice()).willReturn(advisor);
given(advisor.getPointcut()).willReturn(Pointcut.TRUE);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
factory.setAdvisors(advisor);
Flight flight = proxy(factory, this.flight);
flight.getAltitude();
verify(advisor, atLeastOnce()).getPointcut();
}
@Test
public void setTargetVisitorThenUses() {
TargetVisitor visitor = mock(TargetVisitor.class);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
factory.setTargetVisitor(visitor);
factory.proxy(new Flight());
verify(visitor).visit(any(), any());
}
@Test
public void setTargetVisitorIgnoreValueTypesThenIgnores() {
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> ((Integer) factory.proxy(35)).intValue());
factory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
assertThat(factory.proxy(35)).isEqualTo(35);
}
private Authentication authenticated(String user, String... authorities) {
return TestAuthentication.authenticated(TestAuthentication.withUsername(user).authorities(authorities).build());
}
private <T> T proxy(AuthorizationProxyFactory factory, Object target) {
return (T) factory.proxy(target);
}
static class Flight {
@PreAuthorize("hasRole('PILOT')")
Double getAltitude() {
return 35000d;
}
}
interface Identifiable {
@PreAuthorize("authentication.name == this.id || hasRole('ADMIN')")
String getFirstName();
@PreAuthorize("authentication.name == this.id || hasRole('ADMIN')")
String getLastName();
}
public static class User implements Identifiable, Comparable<User> {
private final String id;
private final String firstName;
private final String lastName;
User(String id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public String getId() {
return this.id;
}
@Override
public String getFirstName() {
return this.firstName;
}
@Override
public String getLastName() {
return this.lastName;
}
@Override
public int compareTo(@NotNull User that) {
return this.id.compareTo(that.getId());
}
}
static class UserRepository implements Iterable<User> {
List<User> users = List.of(new User("1", "first", "last"));
@NotNull
@Override
public Iterator<User> iterator() {
return this.users.iterator();
}
}
interface HasSecret {
String secret();
}
record Repository(@PreAuthorize("hasRole('ADMIN')") String secret) implements HasSecret {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 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.
@@ -36,16 +36,6 @@ class AuthorizationManagersTests {
assertThat(decision.isGranted()).isTrue();
}
@Test
void checkAnyOfWithAllAbstainDefaultDecisionWhenOneGrantedThenGrantedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false);
AuthorizationManager<?> composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision,
(a, o) -> new AuthorizationDecision(false), (a, o) -> new AuthorizationDecision(true));
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
// gh-13069
@Test
void checkAnyOfWhenAllNonAbstainingDeniesThenDeniedDecision() {
@@ -64,58 +54,6 @@ class AuthorizationManagersTests {
assertThat(decision.isGranted()).isFalse();
}
@Test
void checkAnyOfWithAllAbstainDefaultDecisionIsDeniedWhenEmptyThenDeniedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false);
AuthorizationManager<?> composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
void checkAnyOfWithAllAbstainDefaultDecisionIsGrantedWhenEmptyThenGrantedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true);
AuthorizationManager<?> composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
void checkAnyOfWithAllAbstainDefaultDecisionIsAbstainWhenEmptyThenAbstainDecision() {
AuthorizationDecision allAbstainDefaultDecision = null;
AuthorizationManager<?> composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNull();
}
@Test
void checkAnyOfWhenAllAbstainDefaultDecisionIsGrantedAndAllManagersAbstainThenGrantedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true);
AuthorizationManager<?> composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision, (a, o) -> null);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
void checkAnyOfWhenAllAbstainDefaultDecisionIsDeniedAndAllManagersAbstainThenDeniedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false);
AuthorizationManager<?> composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision, (a, o) -> null);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
void checkAnyOfWhenAllAbstainDefaultDecisionIsAbstainAndAllManagersAbstainThenAbstainDecision() {
AuthorizationDecision allAbstainDefaultDecision = null;
AuthorizationManager<?> composed = AuthorizationManagers.anyOf(allAbstainDefaultDecision, (a, o) -> null);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNull();
}
@Test
void checkAllOfWhenAllGrantedThenGrantedDecision() {
AuthorizationManager<?> composed = AuthorizationManagers.allOf((a, o) -> new AuthorizationDecision(true),
@@ -125,16 +63,6 @@ class AuthorizationManagersTests {
assertThat(decision.isGranted()).isTrue();
}
@Test
void checkAllOfWithAllAbstainDefaultDecisionWhenAllGrantedThenGrantedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false);
AuthorizationManager<?> composed = AuthorizationManagers.allOf(allAbstainDefaultDecision,
(a, o) -> new AuthorizationDecision(true), (a, o) -> new AuthorizationDecision(true));
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
// gh-13069
@Test
void checkAllOfWhenAllNonAbstainingGrantsThenGrantedDecision() {
@@ -154,16 +82,6 @@ class AuthorizationManagersTests {
assertThat(decision.isGranted()).isFalse();
}
@Test
void checkAllOfWithAllAbstainDefaultDecisionWhenOneDeniedThenDeniedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true);
AuthorizationManager<?> composed = AuthorizationManagers.allOf(allAbstainDefaultDecision,
(a, o) -> new AuthorizationDecision(true), (a, o) -> new AuthorizationDecision(false));
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
void checkAllOfWhenEmptyThenGrantedDecision() {
AuthorizationManager<?> composed = AuthorizationManagers.allOf();
@@ -172,71 +90,4 @@ class AuthorizationManagersTests {
assertThat(decision.isGranted()).isTrue();
}
@Test
void checkAllOfWithAllAbstainDefaultDecisionIsDeniedWhenEmptyThenDeniedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false);
AuthorizationManager<?> composed = AuthorizationManagers.allOf(allAbstainDefaultDecision);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
void checkAllOfWithAllAbstainDefaultDecisionIsGrantedWhenEmptyThenGrantedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true);
AuthorizationManager<?> composed = AuthorizationManagers.allOf(allAbstainDefaultDecision);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
void checkAllOfWithAllAbstainDefaultDecisionIsAbstainWhenEmptyThenAbstainDecision() {
AuthorizationDecision allAbstainDefaultDecision = null;
AuthorizationManager<?> composed = AuthorizationManagers.allOf(allAbstainDefaultDecision);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNull();
}
@Test
void checkAllOfWhenAllAbstainDefaultDecisionIsDeniedAndAllManagersAbstainThenDeniedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(false);
AuthorizationManager<?> composed = AuthorizationManagers.allOf(allAbstainDefaultDecision, (a, o) -> null);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
void checkAllOfWhenAllAbstainDefaultDecisionIsGrantedAndAllManagersAbstainThenGrantedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true);
AuthorizationManager<?> composed = AuthorizationManagers.allOf(allAbstainDefaultDecision, (a, o) -> null);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
void checkAllOfWhenAllAbstainDefaultDecisionIsAbstainAndAllManagersAbstainThenAbstainDecision() {
AuthorizationDecision allAbstainDefaultDecision = null;
AuthorizationManager<?> composed = AuthorizationManagers.allOf(allAbstainDefaultDecision, (a, o) -> null);
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNull();
}
@Test
void checkNotWhenEmptyThenAbstainedDecision() {
AuthorizationManager<?> negated = AuthorizationManagers.not((a, o) -> null);
AuthorizationDecision decision = negated.check(null, null);
assertThat(decision).isNull();
}
@Test
void checkNotWhenGrantedThenDeniedDecision() {
AuthorizationManager<?> negated = AuthorizationManagers.not((a, o) -> new AuthorizationDecision(true));
AuthorizationDecision decision = negated.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
}

View File

@@ -1,227 +0,0 @@
/*
* Copyright 2002-2024 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.Iterator;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.aop.Pointcut;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class ReactiveAuthorizationAdvisorProxyFactoryTests {
private final Authentication user = TestAuthentication.authenticatedUser();
private final Authentication admin = TestAuthentication.authenticatedAdmin();
private final Flight flight = new Flight();
private final User alan = new User("alan", "alan", "turing");
@Test
public void proxyWhenPreAuthorizeThenHonors() {
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
Flight flight = new Flight();
StepVerifier
.create(flight.getAltitude().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
.expectNext(35000d)
.verifyComplete();
Flight secured = proxy(factory, flight);
StepVerifier
.create(secured.getAltitude().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
.verifyError(AccessDeniedException.class);
}
@Test
public void proxyWhenPreAuthorizeOnInterfaceThenHonors() {
SecurityContextHolder.getContext().setAuthentication(this.user);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
StepVerifier
.create(this.alan.getFirstName().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
.expectNext("alan")
.verifyComplete();
User secured = proxy(factory, this.alan);
StepVerifier
.create(secured.getFirstName().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
.verifyError(AccessDeniedException.class);
StepVerifier
.create(secured.getFirstName()
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authenticated("alan"))))
.expectNext("alan")
.verifyComplete();
StepVerifier
.create(secured.getFirstName().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.admin)))
.expectNext("alan")
.verifyComplete();
}
@Test
public void proxyWhenPreAuthorizeOnRecordThenHonors() {
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
HasSecret repo = new Repository(Mono.just("secret"));
StepVerifier.create(repo.secret().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
.expectNext("secret")
.verifyComplete();
HasSecret secured = proxy(factory, repo);
StepVerifier.create(secured.secret().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
.verifyError(AccessDeniedException.class);
StepVerifier.create(secured.secret().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.admin)))
.expectNext("secret")
.verifyComplete();
}
@Test
public void proxyWhenPreAuthorizeOnFluxThenHonors() {
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
Flux<Flight> flights = Flux.just(this.flight);
Flux<Flight> secured = proxy(factory, flights);
StepVerifier
.create(secured.flatMap(Flight::getAltitude)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
.verifyError(AccessDeniedException.class);
}
@Test
public void proxyWhenPreAuthorizeForClassThenHonors() {
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
Class<Flight> clazz = proxy(factory, Flight.class);
assertThat(clazz.getSimpleName()).contains("SpringCGLIB$$");
Flight secured = proxy(factory, this.flight);
StepVerifier
.create(secured.getAltitude().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
.verifyError(AccessDeniedException.class);
}
@Test
public void setAdvisorsWhenProxyThenVisits() {
AuthorizationAdvisor advisor = mock(AuthorizationAdvisor.class);
given(advisor.getAdvice()).willReturn(advisor);
given(advisor.getPointcut()).willReturn(Pointcut.TRUE);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
factory.setAdvisors(advisor);
Flight flight = proxy(factory, this.flight);
flight.getAltitude();
verify(advisor, atLeastOnce()).getPointcut();
}
private Authentication authenticated(String user, String... authorities) {
return TestAuthentication.authenticated(TestAuthentication.withUsername(user).authorities(authorities).build());
}
private <T> T proxy(AuthorizationProxyFactory factory, Object target) {
return (T) factory.proxy(target);
}
static class Flight {
@PreAuthorize("hasRole('PILOT')")
Mono<Double> getAltitude() {
return Mono.just(35000d);
}
}
interface Identifiable {
@PreAuthorize("authentication.name == this.id || hasRole('ADMIN')")
Mono<String> getFirstName();
@PreAuthorize("authentication.name == this.id || hasRole('ADMIN')")
Mono<String> getLastName();
}
public static class User implements Identifiable, Comparable<User> {
private final String id;
private final String firstName;
private final String lastName;
User(String id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public String getId() {
return this.id;
}
@Override
public Mono<String> getFirstName() {
return Mono.just(this.firstName);
}
@Override
public Mono<String> getLastName() {
return Mono.just(this.lastName);
}
@Override
public int compareTo(@NotNull User that) {
return this.id.compareTo(that.getId());
}
}
static class UserRepository implements Iterable<User> {
List<User> users = List.of(new User("1", "first", "last"));
Flux<User> findAll() {
return Flux.fromIterable(this.users);
}
@NotNull
@Override
public Iterator<User> iterator() {
return this.users.iterator();
}
}
interface HasSecret {
Mono<String> secret();
}
record Repository(@PreAuthorize("hasRole('ADMIN')") Mono<String> secret) implements HasSecret {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -16,26 +16,18 @@
package org.springframework.security.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.prepost.PreAuthorize;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
* Tests for {@link AuthorizationAnnotationUtils}.
*
* @author Josh Cummings
* @author Sam Brannen
* Tests for {@link AuthorizationAnnotationUtils}
*/
class AuthorizationAnnotationUtilsTests {
@@ -45,56 +37,15 @@ class AuthorizationAnnotationUtilsTests {
Thread.currentThread().getContextClassLoader(), new Class[] { StringRepository.class },
(p, m, args) -> null);
Method method = proxy.getClass().getDeclaredMethod("findAll");
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')");
assertThatNoException()
.isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class));
}
@Test // gh-13625
void annotationsFromSuperSuperInterfaceShouldNotTriggerAnnotationConfigurationException() throws Exception {
Method method = HelloImpl.class.getDeclaredMethod("sayHello");
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')");
}
@Test
void multipleIdenticalAnnotationsOnClassShouldNotTriggerAnnotationConfigurationException() {
Class<?> clazz = MultipleIdenticalPreAuthorizeAnnotationsOnClass.class;
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, PreAuthorize.class);
assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')");
}
@Test
void multipleIdenticalAnnotationsOnMethodShouldNotTriggerAnnotationConfigurationException() throws Exception {
Method method = MultipleIdenticalPreAuthorizeAnnotationsOnMethod.class.getDeclaredMethod("method");
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
assertThat(preAuthorize.value()).isEqualTo("hasRole('someRole')");
}
@Test
void competingAnnotationsOnClassShouldTriggerAnnotationConfigurationException() {
Class<?> clazz = CompetingPreAuthorizeAnnotationsOnClass.class;
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, PreAuthorize.class))
.withMessageContainingAll("Found 2 competing annotations:", "someRole", "otherRole");
}
@Test
void competingAnnotationsOnMethodShouldTriggerAnnotationConfigurationException() throws Exception {
Method method = CompetingPreAuthorizeAnnotationsOnMethod.class.getDeclaredMethod("method");
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class))
.withMessageContainingAll("Found 2 competing annotations:", "someRole", "otherRole");
}
@Test
void composedMergedAnnotationsAreNotSupported() {
Class<?> clazz = ComposedPreAuthAnnotationOnClass.class;
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, PreAuthorize.class);
// If you comment out .map(MergedAnnotation::withNonMergedAttributes) in
// AuthorizationAnnotationUtils.findDistinctAnnotation(), the value of
// the merged annotation would be "hasRole('composedRole')".
assertThat(preAuthorize.value()).isEqualTo("hasRole('metaRole')");
Method method = HelloImpl.class.getMethod("sayHello");
assertThatNoException()
.isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class));
}
private interface BaseRepository<T> {
@@ -131,60 +82,4 @@ class AuthorizationAnnotationUtilsTests {
}
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('someRole')")
private @interface RequireSomeRole {
}
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('otherRole')")
private @interface RequireOtherRole {
}
@RequireSomeRole
@PreAuthorize("hasRole('someRole')")
private static class MultipleIdenticalPreAuthorizeAnnotationsOnClass {
}
private static class MultipleIdenticalPreAuthorizeAnnotationsOnMethod {
@RequireSomeRole
@PreAuthorize("hasRole('someRole')")
void method() {
}
}
@RequireOtherRole
@PreAuthorize("hasRole('someRole')")
private static class CompetingPreAuthorizeAnnotationsOnClass {
}
private static class CompetingPreAuthorizeAnnotationsOnMethod {
@RequireOtherRole
@PreAuthorize("hasRole('someRole')")
void method() {
}
}
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('metaRole')")
private @interface ComposedPreAuth {
@AliasFor(annotation = PreAuthorize.class)
String value();
}
@ComposedPreAuth("hasRole('composedRole')")
private static class ComposedPreAuthAnnotationOnClass {
}
}

View File

@@ -26,10 +26,8 @@ import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
@@ -38,7 +36,6 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
@@ -142,24 +139,4 @@ public class AuthorizationManagerAfterMethodInterceptorTests {
any(AuthorizationDecision.class));
}
@Test
public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() throws Throwable {
MethodInvocation mi = mock(MethodInvocation.class);
given(mi.proceed()).willReturn("ok");
AuthorizationManager<MethodInvocationResult> manager = mock(AuthorizationManager.class);
given(manager.check(any(), any()))
.willThrow(new MyAuthzDeniedException("denied", new AuthorizationDecision(false)));
AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor(
Pointcut.TRUE, manager);
assertThatExceptionOfType(MyAuthzDeniedException.class).isThrownBy(() -> advice.invoke(mi));
}
static class MyAuthzDeniedException extends AuthorizationDeniedException {
MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) {
super(msg, authorizationResult);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -19,16 +19,12 @@ package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import static org.assertj.core.api.Assertions.assertThat;
@@ -70,15 +66,14 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), any()))
.willReturn(Mono.just(new AuthorizationDecision(true)));
given(mockReactiveAuthorizationManager.verify(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block)
.isEqualTo("john");
verify(mockReactiveAuthorizationManager).check(any(), any());
verify(mockReactiveAuthorizationManager).verify(any(), any());
}
@Test
@@ -88,8 +83,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), any()))
.willReturn(Mono.just(new AuthorizationDecision(true)));
given(mockReactiveAuthorizationManager.verify(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -97,7 +91,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
.extracting(Flux::collectList)
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class))
.containsExactly("john", "bob");
verify(mockReactiveAuthorizationManager, times(2)).check(any(), any());
verify(mockReactiveAuthorizationManager, times(2)).verify(any(), any());
}
@Test
@@ -107,8 +101,8 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), any()))
.willReturn(Mono.just(new AuthorizationDecision(false)));
given(mockReactiveAuthorizationManager.verify(any(), any()))
.willReturn(Mono.error(new AccessDeniedException("Access Denied")));
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -116,158 +110,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
.isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block))
.withMessage("Access Denied");
verify(mockReactiveAuthorizationManager).check(any(), any());
}
@Test
public void invokeFluxWhenAllValuesDeniedAndPostProcessorThenPostProcessorAppliedToEachValueEmitted()
throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
.willAnswer(this::masking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class))
.extracting(Flux::collectList)
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class))
.containsExactly("john-masked", "bob-masked");
verify(mockReactiveAuthorizationManager, times(2)).check(any(), any());
}
@Test
public void invokeFluxWhenOneValueDeniedAndPostProcessorThenPostProcessorAppliedToDeniedValue() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
.willAnswer((invocation) -> {
MethodInvocationResult argument = invocation.getArgument(0);
if (!"john".equals(argument.getResult())) {
return monoMasking(invocation);
}
return Mono.just(argument.getResult());
});
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class))
.extracting(Flux::collectList)
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class))
.containsExactly("john", "bob-masked");
verify(mockReactiveAuthorizationManager, times(2)).check(any(), any());
}
@Test
public void invokeMonoWhenPostProcessableDecisionThenPostProcess() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
.willAnswer(this::masking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block)
.isEqualTo("john-masked");
verify(mockReactiveAuthorizationManager).check(any(), any());
}
@Test
public void invokeMonoWhenPostProcessableDecisionAndPostProcessResultIsMonoThenPostProcessWorks() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
.willAnswer(this::monoMasking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block)
.isEqualTo("john-masked");
verify(mockReactiveAuthorizationManager).check(any(), any());
}
@Test
public void invokeMonoWhenPostProcessableDecisionAndPostProcessResultIsNullThenPostProcessWorks() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
.willReturn(null);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block)
.isEqualTo(null);
verify(mockReactiveAuthorizationManager).check(any(), any());
}
@Test
public void invokeMonoWhenEmptyDecisionThenUseDefaultPostProcessor() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThatExceptionOfType(AuthorizationDeniedException.class)
.isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block))
.withMessage("Access Denied");
verify(mockReactiveAuthorizationManager).check(any(), any());
}
@Test
public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("ok"));
ReactiveAuthorizationManager<MethodInvocationResult> manager = mock(ReactiveAuthorizationManager.class);
given(manager.check(any(), any()))
.willReturn(Mono.error(new MyAuthzDeniedException("denied", new AuthorizationDecision(false))));
AuthorizationManagerAfterReactiveMethodInterceptor advice = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, manager);
assertThatExceptionOfType(MyAuthzDeniedException.class)
.isThrownBy(() -> ((Mono<?>) advice.invoke(mockMethodInvocation)).block());
}
private Object masking(InvocationOnMock invocation) {
MethodInvocationResult result = invocation.getArgument(0);
return result.getResult() + "-masked";
}
private Object monoMasking(InvocationOnMock invocation) {
MethodInvocationResult result = invocation.getArgument(0);
return Mono.just(result.getResult() + "-masked");
}
interface HandlingReactiveAuthorizationManager
extends ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedHandler {
verify(mockReactiveAuthorizationManager).verify(any(), any());
}
class Sample {
@@ -282,12 +125,4 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
}
static class MyAuthzDeniedException extends AuthorizationDeniedException {
MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) {
super(msg, authorizationResult);
}
}
}

View File

@@ -25,10 +25,8 @@ import org.springframework.aop.Pointcut;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
@@ -36,7 +34,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -136,22 +133,4 @@ public class AuthorizationManagerBeforeMethodInterceptorTests {
any(AuthorizationDecision.class));
}
@Test
public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() {
AuthorizationManager<MethodInvocation> manager = mock(AuthorizationManager.class);
given(manager.check(any(), any()))
.willThrow(new MyAuthzDeniedException("denied", new AuthorizationDecision(false)));
AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(
Pointcut.TRUE, manager);
assertThatExceptionOfType(MyAuthzDeniedException.class).isThrownBy(() -> advice.invoke(null));
}
static class MyAuthzDeniedException extends AuthorizationDeniedException {
MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) {
super(msg, authorizationResult);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -25,9 +25,6 @@ import reactor.core.publisher.Mono;
import org.springframework.aop.Pointcut;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import static org.assertj.core.api.Assertions.assertThat;
@@ -70,15 +67,14 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation)))
.willReturn(Mono.just(new AuthorizationDecision(true)));
given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block)
.isEqualTo("john");
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation));
}
@Test
@@ -88,8 +84,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation)))
.willReturn(Mono.just(new AuthorizationDecision((true))));
given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -97,7 +92,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
.extracting(Flux::collectList)
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class))
.containsExactly("john", "bob");
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation));
}
@Test
@@ -107,8 +102,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation)))
.willReturn(Mono.just(new AuthorizationDecision(false)));
given(mockReactiveAuthorizationManager.verify(any(), eq(mockMethodInvocation)))
.willReturn(Mono.error(new AccessDeniedException("Access Denied")));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@@ -116,119 +111,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
.isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block))
.withMessage("Access Denied");
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
}
@Test
public void invokeMonoWhenDeniedAndPostProcessorThenInvokePostProcessor() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class)))
.willReturn("***");
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block)
.isEqualTo("***");
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
}
@Test
public void invokeMonoWhenDeniedAndMonoPostProcessorThenInvokePostProcessor() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class)))
.willReturn(Mono.just("***"));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block)
.isEqualTo("***");
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
}
@Test
public void invokeFluxWhenDeniedAndPostProcessorThenInvokePostProcessor() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class)))
.willReturn(Mono.just("***"));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class))
.extracting(Flux::collectList)
.extracting(Mono::block, InstanceOfAssertFactories.list(String.class))
.containsExactly("***");
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
}
@Test
public void invokeMonoWhenEmptyDecisionThenInvokeDefaultPostProcessor() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThatExceptionOfType(AuthorizationDeniedException.class)
.isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Mono.class))
.extracting(Mono::block))
.withMessage("Access Denied");
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
}
@Test
public void invokeFluxWhenEmptyDecisionThenInvokeDefaultPostProcessor() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
ReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
assertThatExceptionOfType(AuthorizationDeniedException.class)
.isThrownBy(() -> assertThat(result).asInstanceOf(InstanceOfAssertFactories.type(Flux.class))
.extracting(Flux::blockFirst))
.withMessage("Access Denied");
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
}
@Test
public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
ReactiveAuthorizationManager<MethodInvocation> manager = mock(ReactiveAuthorizationManager.class);
given(manager.check(any(), any()))
.willThrow(new MyAuthzDeniedException("denied", new AuthorizationDecision(false)));
AuthorizationManagerBeforeReactiveMethodInterceptor advice = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, manager);
assertThatExceptionOfType(MyAuthzDeniedException.class)
.isThrownBy(() -> ((Mono<?>) advice.invoke(mockMethodInvocation)).block());
}
interface HandlingReactiveAuthorizationManager
extends ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation));
}
class Sample {
@@ -243,12 +126,4 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
}
static class MyAuthzDeniedException extends AuthorizationDeniedException {
MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) {
super(msg, authorizationResult);
}
}
}

View File

@@ -1,70 +0,0 @@
/*
* Copyright 2002-2024 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.method;
import org.junit.jupiter.api.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
import static org.assertj.core.api.Assertions.assertThat;
public class ExpressionUtilsTests {
private final Object details = new Object();
@Test
public void evaluateWhenAuthorizationDecisionThenReturns() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#root.returnDecision()");
StandardEvaluationContext context = new StandardEvaluationContext(this);
assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(AuthorizationDecisionDetails.class)
.extracting("details")
.isEqualTo(this.details);
}
@Test
public void evaluateWhenBooleanThenReturnsExpressionAuthorizationDecision() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#root.returnResult()");
StandardEvaluationContext context = new StandardEvaluationContext(this);
assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(ExpressionAuthorizationDecision.class);
}
public AuthorizationDecision returnDecision() {
return new AuthorizationDecisionDetails(false, this.details);
}
public boolean returnResult() {
return false;
}
static final class AuthorizationDecisionDetails extends AuthorizationDecision {
final Object details;
AuthorizationDecisionDetails(boolean granted, Object details) {
super(granted);
this.details = details;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -225,56 +225,6 @@ public class Jsr250AuthorizationManagerTests {
.isThrownBy(() -> manager.check(authentication, methodInvocation));
}
@Test
public void checkRequiresUserWhenMethodsFromInheritThenApplies() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new RolesAllowedClass(),
RolesAllowedClass.class, "securedUser");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkPermitAllWhenMethodsFromInheritThenApplies() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new PermitAllClass(), PermitAllClass.class,
"securedUser");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkDenyAllWhenMethodsFromInheritThenApplies() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new DenyAllClass(), DenyAllClass.class,
"securedUser");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision.isGranted()).isFalse();
}
@RolesAllowed("USER")
public static class RolesAllowedClass extends ParentClass {
}
@PermitAll
public static class PermitAllClass extends ParentClass {
}
@DenyAll
public static class DenyAllClass extends ParentClass {
}
public static class ParentClass {
public void securedUser() {
}
}
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
public void doSomething() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -167,29 +167,6 @@ public class PostAuthorizeAuthorizationManagerTests {
.isThrownBy(() -> manager.check(authentication, result));
}
@Test
public void checkRequiresUserWhenMethodsFromInheritThenApplies() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new PostAuthorizeClass(),
PostAuthorizeClass.class, "securedUser");
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
assertThat(decision.isGranted()).isTrue();
}
@PostAuthorize("hasRole('USER')")
public static class PostAuthorizeClass extends ParentClass {
}
public static class ParentClass {
public void securedUser() {
}
}
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
public void doSomething() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -170,34 +170,6 @@ public class PostFilterAuthorizationMethodInterceptorTests {
SecurityContextHolder.setContextHolderStrategy(saved);
}
@Test
public void checkPostFilterWhenMethodsFromInheritThenApplies() throws Throwable {
String[] array = { "john", "bob" };
MockMethodInvocation methodInvocation = new MockMethodInvocation(new PostFilterClass(), PostFilterClass.class,
"inheritMethod", new Class[] { String[].class }, new Object[] { array }) {
@Override
public Object proceed() {
return array;
}
};
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
Object result = advice.invoke(methodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.array(String[].class)).containsOnly("john");
}
@PostFilter("filterObject == 'john'")
public static class PostFilterClass extends ParentClass {
}
public static class ParentClass {
public String[] inheritMethod(String[] array) {
return array;
}
}
@PostFilter("filterObject == 'john'")
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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.
@@ -147,28 +147,6 @@ public class PreAuthorizeAuthorizationManagerTests {
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRequiresUserWhenMethodsFromInheritThenApplies() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new PreAuthorizeClass(),
PreAuthorizeClass.class, "securedUser");
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision.isGranted()).isTrue();
}
@PreAuthorize("hasRole('USER')")
public static class PreAuthorizeClass extends ParentClass {
}
public static class ParentClass {
public void securedUser() {
}
}
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
public void doSomething() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -224,32 +224,6 @@ public class PreFilterAuthorizationMethodInterceptorTests {
SecurityContextHolder.setContextHolderStrategy(saved);
}
@Test
public void checkPreFilterWhenMethodsFromInheritThenApplies() throws Throwable {
List<String> list = new ArrayList<>();
list.add("john");
list.add("bob");
MockMethodInvocation invocation = new MockMethodInvocation(new PreFilterClass(), PreFilterClass.class,
"inheritMethod", new Class[] { List.class }, new Object[] { list });
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
advice.invoke(invocation);
assertThat(list).hasSize(1);
assertThat(list.get(0)).isEqualTo("john");
}
@PreFilter("filterObject == 'john'")
public static class PreFilterClass extends ParentClass {
}
public static class ParentClass {
public void inheritMethod(List<String> list) {
}
}
@PreFilter("filterObject == 'john'")
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 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.
@@ -167,28 +167,6 @@ public class SecuredAuthorizationManagerTests {
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRequiresUserWhenMethodsFromInheritThenApplies() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new SecuredSonClass(), SecuredSonClass.class,
"securedUser");
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision.isGranted()).isTrue();
}
@Secured("ROLE_USER")
public static class SecuredSonClass extends ParentClass {
}
public static class ParentClass {
public void securedUser() {
}
}
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
public void doSomething() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2013 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.
@@ -23,7 +23,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
@@ -80,7 +79,6 @@ public class SpringSecurityCoreVersionTests {
}
@Test
@Disabled("Since 6.3. See gh-3737")
public void serialVersionMajorAndMinorVersionMatchBuildVersion() {
String version = System.getProperty("springSecurityVersion");
// Strip patch version