Revert unnecessary commits from main
Issue gh-15016
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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 > ROLE_B
|
||||
* ROLE_B > ROLE_AUTHENTICATED
|
||||
* ROLE_AUTHENTICATED > 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
* @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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + ']';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user