16 changed files with 1360 additions and 27 deletions
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/* |
||||
* 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.config.annotation.method.configuration; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.aop.framework.AopInfrastructureBean; |
||||
import org.springframework.beans.factory.ObjectProvider; |
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Role; |
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator; |
||||
import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory; |
||||
import org.springframework.security.authorization.method.AuthorizationAdvisor; |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
final class AuthorizationProxyConfiguration implements AopInfrastructureBean { |
||||
|
||||
@Bean |
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) |
||||
static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider<AuthorizationAdvisor> provider) { |
||||
List<AuthorizationAdvisor> advisors = new ArrayList<>(); |
||||
provider.forEach(advisors::add); |
||||
AnnotationAwareOrderComparator.sort(advisors); |
||||
return new AuthorizationAdvisorProxyFactory(advisors); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
/* |
||||
* 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.config.annotation.method.configuration; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.security.access.AccessDeniedException; |
||||
import org.springframework.security.access.prepost.PostAuthorize; |
||||
import org.springframework.security.access.prepost.PreAuthorize; |
||||
import org.springframework.security.authorization.AuthorizationProxyFactory; |
||||
import org.springframework.security.config.test.SpringTestContext; |
||||
import org.springframework.security.config.test.SpringTestContextExtension; |
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; |
||||
import org.springframework.security.test.context.support.WithMockUser; |
||||
import org.springframework.test.context.junit.jupiter.SpringExtension; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link PrePostMethodSecurityConfiguration}. |
||||
* |
||||
* @author Evgeniy Cheban |
||||
* @author Josh Cummings |
||||
*/ |
||||
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) |
||||
@SecurityTestExecutionListeners |
||||
public class AuthorizationProxyConfigurationTests { |
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this); |
||||
|
||||
@Autowired |
||||
AuthorizationProxyFactory proxyFactory; |
||||
|
||||
@WithMockUser |
||||
@Test |
||||
public void proxyWhenNotPreAuthorizedThenDenies() { |
||||
this.spring.register(DefaultsConfig.class).autowire(); |
||||
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster()); |
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::makeToast) |
||||
.withMessage("Access Denied"); |
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::extractBread) |
||||
.withMessage("Access Denied"); |
||||
} |
||||
|
||||
@WithMockUser(roles = "ADMIN") |
||||
@Test |
||||
public void proxyWhenPreAuthorizedThenAllows() { |
||||
this.spring.register(DefaultsConfig.class).autowire(); |
||||
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster()); |
||||
toaster.makeToast(); |
||||
assertThat(toaster.extractBread()).isEqualTo("yummy"); |
||||
} |
||||
|
||||
@EnableMethodSecurity |
||||
@Configuration |
||||
static class DefaultsConfig { |
||||
|
||||
} |
||||
|
||||
static class Toaster { |
||||
|
||||
@PreAuthorize("hasRole('ADMIN')") |
||||
void makeToast() { |
||||
|
||||
} |
||||
|
||||
@PostAuthorize("hasRole('ADMIN')") |
||||
String extractBread() { |
||||
return "yummy"; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,308 @@
@@ -0,0 +1,308 @@
|
||||
/* |
||||
* 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.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.stream.Stream; |
||||
|
||||
import org.springframework.aop.Advisor; |
||||
import org.springframework.aop.framework.ProxyFactory; |
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator; |
||||
import org.springframework.security.authorization.method.AuthorizationAdvisor; |
||||
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> |
||||
* AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor.preAuthorize(); |
||||
* AuthorizationProxyFactory proxyFactory = new AuthorizationProxyFactory(preAuthorize); |
||||
* 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 { |
||||
|
||||
private final Collection<AuthorizationAdvisor> advisors; |
||||
|
||||
public AuthorizationAdvisorProxyFactory(AuthorizationAdvisor... advisors) { |
||||
this.advisors = List.of(advisors); |
||||
} |
||||
|
||||
public AuthorizationAdvisorProxyFactory(Collection<AuthorizationAdvisor> advisors) { |
||||
this.advisors = List.copyOf(advisors); |
||||
} |
||||
|
||||
/** |
||||
* Create a new {@link AuthorizationAdvisorProxyFactory} that includes the given |
||||
* advisors in addition to any advisors {@code this} instance already has. |
||||
* |
||||
* <p> |
||||
* All advisors are re-sorted by their advisor order. |
||||
* @param advisors the advisors to add |
||||
* @return a new {@link AuthorizationAdvisorProxyFactory} instance |
||||
*/ |
||||
public AuthorizationAdvisorProxyFactory withAdvisors(AuthorizationAdvisor... advisors) { |
||||
List<AuthorizationAdvisor> merged = new ArrayList<>(this.advisors.size() + advisors.length); |
||||
merged.addAll(this.advisors); |
||||
merged.addAll(List.of(advisors)); |
||||
AnnotationAwareOrderComparator.sort(merged); |
||||
return new AuthorizationAdvisorProxyFactory(merged); |
||||
} |
||||
|
||||
/** |
||||
* 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; |
||||
} |
||||
if (target instanceof Class<?> targetClass) { |
||||
return proxyClass(targetClass); |
||||
} |
||||
if (target instanceof Iterator<?> iterator) { |
||||
return proxyIterator(iterator); |
||||
} |
||||
if (target instanceof Queue<?> queue) { |
||||
return proxyQueue(queue); |
||||
} |
||||
if (target instanceof List<?> list) { |
||||
return proxyList(list); |
||||
} |
||||
if (target instanceof SortedSet<?> set) { |
||||
return proxySortedSet(set); |
||||
} |
||||
if (target instanceof Set<?> set) { |
||||
return proxySet(set); |
||||
} |
||||
if (target.getClass().isArray()) { |
||||
return proxyArray((Object[]) target); |
||||
} |
||||
if (target instanceof SortedMap<?, ?> map) { |
||||
return proxySortedMap(map); |
||||
} |
||||
if (target instanceof Iterable<?> iterable) { |
||||
return proxyIterable(iterable); |
||||
} |
||||
if (target instanceof Map<?, ?> map) { |
||||
return proxyMap(map); |
||||
} |
||||
if (target instanceof Stream<?> stream) { |
||||
return proxyStream(stream); |
||||
} |
||||
if (target instanceof Optional<?> optional) { |
||||
return proxyOptional(optional); |
||||
} |
||||
ProxyFactory factory = new ProxyFactory(target); |
||||
for (Advisor advisor : this.advisors) { |
||||
factory.addAdvisors(advisor); |
||||
} |
||||
factory.setProxyTargetClass(!Modifier.isFinal(target.getClass().getModifiers())); |
||||
return factory.getProxy(); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private <T> T proxyCast(T target) { |
||||
return (T) proxy(target); |
||||
} |
||||
|
||||
private Class<?> proxyClass(Class<?> targetClass) { |
||||
ProxyFactory factory = new ProxyFactory(); |
||||
factory.setTargetClass(targetClass); |
||||
factory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass)); |
||||
factory.setProxyTargetClass(!Modifier.isFinal(targetClass.getModifiers())); |
||||
for (Advisor advisor : this.advisors) { |
||||
factory.addAdvisors(advisor); |
||||
} |
||||
return factory.getProxyClass(getClass().getClassLoader()); |
||||
} |
||||
|
||||
private <T> Iterable<T> proxyIterable(Iterable<T> iterable) { |
||||
return () -> proxyIterator(iterable.iterator()); |
||||
} |
||||
|
||||
private <T> Iterator<T> proxyIterator(Iterator<T> iterator) { |
||||
return new Iterator<>() { |
||||
@Override |
||||
public boolean hasNext() { |
||||
return iterator.hasNext(); |
||||
} |
||||
|
||||
@Override |
||||
public T next() { |
||||
return proxyCast(iterator.next()); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
private <T> SortedSet<T> proxySortedSet(SortedSet<T> set) { |
||||
SortedSet<T> proxies = new TreeSet<>(set.comparator()); |
||||
for (T toProxy : set) { |
||||
proxies.add(proxyCast(toProxy)); |
||||
} |
||||
try { |
||||
set.clear(); |
||||
set.addAll(proxies); |
||||
return proxies; |
||||
} |
||||
catch (UnsupportedOperationException ex) { |
||||
return Collections.unmodifiableSortedSet(proxies); |
||||
} |
||||
} |
||||
|
||||
private <T> Set<T> proxySet(Set<T> set) { |
||||
Set<T> proxies = new LinkedHashSet<>(set.size()); |
||||
for (T toProxy : set) { |
||||
proxies.add(proxyCast(toProxy)); |
||||
} |
||||
try { |
||||
set.clear(); |
||||
set.addAll(proxies); |
||||
return proxies; |
||||
} |
||||
catch (UnsupportedOperationException ex) { |
||||
return Collections.unmodifiableSet(proxies); |
||||
} |
||||
} |
||||
|
||||
private <T> Queue<T> proxyQueue(Queue<T> queue) { |
||||
Queue<T> proxies = new LinkedList<>(); |
||||
for (T toProxy : queue) { |
||||
proxies.add(proxyCast(toProxy)); |
||||
} |
||||
queue.clear(); |
||||
queue.addAll(proxies); |
||||
return proxies; |
||||
} |
||||
|
||||
private <T> List<T> proxyList(List<T> list) { |
||||
List<T> proxies = new ArrayList<>(list.size()); |
||||
for (T toProxy : list) { |
||||
proxies.add(proxyCast(toProxy)); |
||||
} |
||||
try { |
||||
list.clear(); |
||||
list.addAll(proxies); |
||||
return proxies; |
||||
} |
||||
catch (UnsupportedOperationException ex) { |
||||
return Collections.unmodifiableList(proxies); |
||||
} |
||||
} |
||||
|
||||
private Object[] proxyArray(Object[] objects) { |
||||
List<Object> retain = new ArrayList<>(objects.length); |
||||
for (Object object : objects) { |
||||
retain.add(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(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(entry.getValue())); |
||||
} |
||||
try { |
||||
entries.clear(); |
||||
entries.putAll(proxies); |
||||
return entries; |
||||
} |
||||
catch (UnsupportedOperationException ex) { |
||||
return Collections.unmodifiableSortedMap(proxies); |
||||
} |
||||
} |
||||
|
||||
private <K, V> Map<K, V> proxyMap(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(entry.getValue())); |
||||
} |
||||
try { |
||||
entries.clear(); |
||||
entries.putAll(proxies); |
||||
return entries; |
||||
} |
||||
catch (UnsupportedOperationException ex) { |
||||
return Collections.unmodifiableMap(proxies); |
||||
} |
||||
} |
||||
|
||||
private Stream<?> proxyStream(Stream<?> stream) { |
||||
return stream.map(this::proxy).onClose(stream::close); |
||||
} |
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") |
||||
private Optional<?> proxyOptional(Optional<?> optional) { |
||||
return optional.map(this::proxy); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
/* |
||||
* 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 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); |
||||
|
||||
} |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
/* |
||||
* 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 { |
||||
|
||||
} |
||||
@ -0,0 +1,431 @@
@@ -0,0 +1,431 @@
|
||||
/* |
||||
* 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.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.AuthorizationManagerBeforeMethodInterceptor; |
||||
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.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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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 proxyWhenPreAuthorizeForStreamThenHonors() { |
||||
SecurityContextHolder.getContext().setAuthentication(this.user); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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); |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
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() { |
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor |
||||
.preAuthorize(); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(preAuthorize); |
||||
Class<Flight> clazz = proxy(factory, Flight.class); |
||||
assertThat(clazz.getSimpleName()).contains("SpringCGLIB$$0"); |
||||
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 withAdvisorsWhenProxyThenVisits() { |
||||
AuthorizationAdvisor advisor = mock(AuthorizationAdvisor.class); |
||||
given(advisor.getAdvice()).willReturn(advisor); |
||||
given(advisor.getPointcut()).willReturn(Pointcut.TRUE); |
||||
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory(); |
||||
factory = factory.withAdvisors(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')") |
||||
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 { |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue