Skip to content

Commit

Permalink
Add Value-Type Ignore Support
Browse files Browse the repository at this point in the history
  • Loading branch information
jzheaux committed Mar 20, 2024
1 parent ce54a6d commit a41e971
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,21 @@
import org.springframework.context.annotation.Role;
import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationProxyFactoryDelegate;
import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;

@Configuration(proxyBeanMethods = false)
final class AuthorizationProxyConfiguration implements AopInfrastructureBean {

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider<AuthorizationAdvisor> provider) {
static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider<AuthorizationAdvisor> provider,
ObjectProvider<AuthorizationProxyFactoryDelegate> delegate) {
List<AuthorizationAdvisor> advisors = new ArrayList<>();
provider.forEach(advisors::add);
AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
factory.setAdvisors(advisors);
delegate.ifAvailable(factory::setProxyFactoryDelegate);
return factory;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.context.annotation.Role;
import org.springframework.security.authorization.ReactiveAuthorizationAdvisorProxyFactory;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationProxyFactoryDelegate;
import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;

@Configuration(proxyBeanMethods = false)
Expand All @@ -37,11 +38,12 @@ final class ReactiveAuthorizationProxyConfiguration implements AopInfrastructure
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static ReactiveAuthorizationAdvisorProxyFactory authorizationProxyFactory(
ObjectProvider<AuthorizationAdvisor> provider) {
ObjectProvider<AuthorizationAdvisor> provider, ObjectProvider<AuthorizationProxyFactoryDelegate> delegate) {
List<AuthorizationAdvisor> advisors = new ArrayList<>();
provider.forEach(advisors::add);
ReactiveAuthorizationAdvisorProxyFactory factory = new ReactiveAuthorizationAdvisorProxyFactory();
factory.setAdvisors(advisors);
delegate.ifAvailable(factory::setProxyFactoryDelegate);
return factory;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationProxyFactoryDelegate;
import org.springframework.security.authorization.method.AuthorizationProxyFactoryDelegates;
import org.springframework.security.authorization.method.AuthorizeReturnObject;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
Expand Down Expand Up @@ -1143,6 +1145,12 @@ List<String> resultsContainDave(List<String> list) {
@Configuration
static class AuthorizeResultConfig {

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static AuthorizationProxyFactoryDelegate ignoreValueTypes() {
return AuthorizationProxyFactoryDelegates.ignoreValueTypes();
}

@Bean
FlightRepository flights() {
FlightRepository flights = new FlightRepository();
Expand Down Expand Up @@ -1186,6 +1194,7 @@ void remove(String id) {

}

@AuthorizeReturnObject
static class Flight {

private final String id;
Expand Down Expand Up @@ -1216,7 +1225,6 @@ Integer getSeats() {
return this.seats;
}

@AuthorizeReturnObject
@PostAuthorize("hasAuthority('seating:read')")
@PostFilter("filterObject.name != 'Kevin Mitnick'")
List<Passenger> getPassengers() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
import reactor.test.StepVerifier;

import org.springframework.beans.factory.annotation.Autowired;
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.expression.EvaluationContext;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.expression.SecurityExpressionRoot;
Expand All @@ -42,6 +44,8 @@
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authorization.method.AuthorizationProxyFactoryDelegate;
import org.springframework.security.authorization.method.AuthorizationProxyFactoryDelegates;
import org.springframework.security.authorization.method.AuthorizeReturnObject;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.test.SpringTestContext;
Expand Down Expand Up @@ -238,6 +242,12 @@ public void bar(String param) {
@Configuration
static class AuthorizeResultConfig {

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static AuthorizationProxyFactoryDelegate skipValueTypes() {
return AuthorizationProxyFactoryDelegates.ignoreValueTypes();
}

@Bean
FlightRepository flights() {
FlightRepository flights = new FlightRepository();
Expand Down Expand Up @@ -282,6 +292,7 @@ Mono<Void> remove(String id) {

}

@AuthorizeReturnObject
static class Flight {

private final String id;
Expand Down Expand Up @@ -312,7 +323,6 @@ Mono<Integer> getSeats() {
return Mono.just(this.seats);
}

@AuthorizeReturnObject
@PostAuthorize("hasAnyAuthority('seating:read', 'airplane:read')")
@PostFilter("@isNotKevin.apply(filterObject)")
Flux<Passenger> getPassengers() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationProxyFactoryDelegate;
import org.springframework.security.authorization.method.AuthorizationProxyFactoryDelegates;
import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
Expand Down Expand Up @@ -78,6 +81,8 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx

private List<AuthorizationAdvisor> advisors = new ArrayList<>();

private AuthorizationProxyFactoryDelegate delegate = AuthorizationProxyFactoryDelegates.ignore((c) -> false);

public AuthorizationAdvisorProxyFactory() {
List<AuthorizationAdvisor> advisors = new ArrayList<>();
advisors.add(AuthorizationManagerBeforeMethodInterceptor.preAuthorize());
Expand Down Expand Up @@ -111,6 +116,9 @@ public Object proxy(Object target) {
if (target == null) {
return null;
}
if (this.delegate.proxies(target)) {
return this.delegate.proxy(this, target);
}
if (target instanceof Class<?> targetClass) {
return proxyClass(targetClass);
}
Expand Down Expand Up @@ -179,6 +187,26 @@ public void setAdvisors(Collection<AuthorizationAdvisor> advisors) {
AnnotationAwareOrderComparator.sort(this.advisors);
}

/**
* Consult this delegate first before exercising the default proxy behavior.
*
* <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 proxy ignore attempts to proxy
* primitives and wrappers, then you can do:
*
* <pre>
* AuthorizationAdvisorProxyFactory proxyFactory = new AuthorizationAdvisorProxyFactory();
* AuthorizationProxyFactoryDelegate delegate = AuthorizationProxyFactoryDelegates.ignoreValueTypes();
* proxyFactory.setProxyFactoryDelegate(delegate);
* </pre>
* @param delegate the delegate to use to introduce specialized behavior for a type
*/
public void setProxyFactoryDelegate(AuthorizationProxyFactoryDelegate delegate) {
Assert.notNull(delegate, "delegate cannot be null");
this.delegate = delegate;
}

@SuppressWarnings("unchecked")
private <T> T proxyCast(T target) {
return (T) proxy(target);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationProxyFactoryDelegate;
import org.springframework.security.authorization.method.AuthorizationProxyFactoryDelegates;
import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;
import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.util.Assert;

/**
* A proxy factory for applying authorization advice to an arbitrary object.
Expand Down Expand Up @@ -67,6 +70,8 @@ public final class ReactiveAuthorizationAdvisorProxyFactory implements Authoriza

private final AuthorizationAdvisorProxyFactory defaults = new AuthorizationAdvisorProxyFactory();

private AuthorizationProxyFactoryDelegate delegate = AuthorizationProxyFactoryDelegates.ignore((c) -> false);

public ReactiveAuthorizationAdvisorProxyFactory() {
List<AuthorizationAdvisor> advisors = new ArrayList<>();
advisors.add(AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize());
Expand Down Expand Up @@ -97,6 +102,12 @@ public ReactiveAuthorizationAdvisorProxyFactory() {
*/
@Override
public Object proxy(Object target) {
if (target == null) {
return null;
}
if (this.delegate.proxies(target)) {
return this.delegate.proxy(this, target);
}
if (target instanceof Mono<?> mono) {
return proxyMono(mono);
}
Expand Down Expand Up @@ -128,6 +139,27 @@ public void setAdvisors(Collection<AuthorizationAdvisor> advisors) {
this.defaults.setAdvisors(advisors);
}

/**
* Consult this delegate first before exercising the default proxy behavior.
*
* <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 proxy ignore attempts to proxy
* primitives and wrappers, then you can do:
*
* <pre>
* ReactiveAuthorizationAdvisorProxyFactory proxyFactory = new ReactiveAuthorizationAdvisorProxyFactory();
* AuthorizationProxyFactoryDelegate delegate = AuthorizationProxyFactoryDelegates.ignoreValueTypes();
* proxyFactory.setProxyFactoryDelegate(delegate);
* </pre>
* @param delegate the delegate to use to introduce specialized behavior for a type
*/
public void setProxyFactoryDelegate(AuthorizationProxyFactoryDelegate delegate) {
Assert.notNull(delegate, "delegate cannot be null");
this.delegate = delegate;
this.defaults.setProxyFactoryDelegate(delegate);
}

private Mono<?> proxyMono(Mono<?> mono) {
return mono.map(this::proxy);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.security.authorization.AuthorizationProxyFactory;

public interface AuthorizationProxyFactoryDelegate {

default boolean proxies(Object object) {
return true;
}

Object proxy(AuthorizationProxyFactory proxyFactory, Object object);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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.util.function.Predicate;

import org.springframework.security.authorization.AuthorizationProxyFactory;
import org.springframework.util.ClassUtils;

public final class AuthorizationProxyFactoryDelegates {

private AuthorizationProxyFactoryDelegates() {

}

public static AuthorizationProxyFactoryDelegate ignoreValueTypes() {
return ignore((c) -> c != Class.class && ClassUtils.isSimpleValueType(c));
}

public static AuthorizationProxyFactoryDelegate ignore(Predicate<Class<?>> ignored) {
return new TypeDelegate(ignored, (factory, object) -> object);
}

private static final class TypeDelegate implements AuthorizationProxyFactoryDelegate {

private final Predicate<Class<?>> returnObjectType;

private final AuthorizationProxyFactoryDelegate delegate;

TypeDelegate(Predicate<Class<?>> returnObjectType, AuthorizationProxyFactoryDelegate delegate) {
this.returnObjectType = returnObjectType;
this.delegate = delegate;
}

@Override
public boolean proxies(Object object) {
return this.returnObjectType.test(object.getClass()) && this.delegate.proxies(object);
}

@Override
public Object proxy(AuthorizationProxyFactory proxyFactory, Object object) {
return this.delegate.proxy(proxyFactory, object);
}

}

}
34 changes: 31 additions & 3 deletions docs/modules/ROOT/pages/servlet/authorization/method-security.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1812,12 +1812,40 @@ fun getEmailWhenProxiedThenAuthorizes() {
----
======

[NOTE]
====
=== Using `@AuthorizeReturnObject` at the class level

`@AuthorizeReturnObject` can be placed at the class level. Note, though, that this means Spring Security will proxy any return object, including ``String``, ``Integer`` and other types.
This is often not what you want to do.

In most cases, you will want to annotate the individual methods.
If you want to use `@AuthorizeReturnObject` on a class or interface whose methods return value types, like `int`, `String`, `Double` or collections of those types, then you should also publish the appropriate `AuthorizationProxyFactoryDelegate` as follows:


[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
static AuthorizationProxyFactoryDelegate ignoreValueTypes() {
return AuthorizationProxyFactoryDelegates.ignoreValueTypes();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun ignoreValueTypes(): AuthorizationProxyFactoryDelegate {
return AuthorizationProxyFactoryDelegates.ignoreValueTypes()
}
----
======

[TIP]
====
You can publish your own `AuthorizationProxyFactoryDelegate` to customize the proxying for any set of types
====

=== Programmatically Proxying
Expand Down

0 comments on commit a41e971

Please sign in to comment.