Skip to content

Commit e29058c

Browse files
committed
Add AuthorizeReturnObject Spring Data Hints
Issue gh-15709
1 parent fd5d03d commit e29058c

File tree

5 files changed

+263
-0
lines changed

5 files changed

+263
-0
lines changed

config/spring-security-config.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies {
2121
api 'org.springframework:spring-context'
2222
api 'org.springframework:spring-core'
2323

24+
optional project(':spring-security-data')
2425
optional project(':spring-security-ldap')
2526
optional project(':spring-security-messaging')
2627
optional project(path: ':spring-security-saml2-service-provider')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.method.configuration;
18+
19+
import org.springframework.aop.framework.AopInfrastructureBean;
20+
import org.springframework.beans.factory.config.BeanDefinition;
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.context.annotation.Role;
24+
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
25+
import org.springframework.security.authorization.AuthorizationProxyFactory;
26+
import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar;
27+
28+
@Configuration(proxyBeanMethods = false)
29+
final class AuthorizationProxyDataConfiguration implements AopInfrastructureBean {
30+
31+
@Bean
32+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
33+
static SecurityHintsRegistrar authorizeReturnObjectDataHintsRegistrar(AuthorizationProxyFactory proxyFactory) {
34+
return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
35+
}
36+
37+
}

config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.context.annotation.ImportSelector;
2727
import org.springframework.core.type.AnnotationMetadata;
2828
import org.springframework.lang.NonNull;
29+
import org.springframework.util.ClassUtils;
2930

3031
/**
3132
* Dynamically determines which imports to include using the {@link EnableMethodSecurity}
@@ -37,6 +38,9 @@
3738
*/
3839
final class MethodSecuritySelector implements ImportSelector {
3940

41+
private static final boolean isDataPresent = ClassUtils
42+
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
43+
4044
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
4145

4246
@Override
@@ -57,6 +61,9 @@ public String[] selectImports(@NonNull AnnotationMetadata importMetadata) {
5761
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
5862
}
5963
imports.add(AuthorizationProxyConfiguration.class.getName());
64+
if (isDataPresent) {
65+
imports.add(AuthorizationProxyDataConfiguration.class.getName());
66+
}
6067
return imports.toArray(new String[0]);
6168
}
6269

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.data.aot.hint;
18+
19+
import java.lang.reflect.Method;
20+
import java.util.ArrayList;
21+
import java.util.HashSet;
22+
import java.util.List;
23+
import java.util.Set;
24+
25+
import org.springframework.aot.hint.RuntimeHints;
26+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
27+
import org.springframework.core.ResolvableType;
28+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
29+
import org.springframework.security.aot.hint.AuthorizeReturnObjectCoreHintsRegistrar;
30+
import org.springframework.security.aot.hint.AuthorizeReturnObjectHintsRegistrar;
31+
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
32+
import org.springframework.security.authorization.AuthorizationProxyFactory;
33+
import org.springframework.security.authorization.method.AuthorizeReturnObject;
34+
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
35+
import org.springframework.security.core.annotation.SecurityAnnotationScanners;
36+
37+
/**
38+
* A {@link SecurityHintsRegistrar} that scans all beans for implementations of
39+
* {@link RepositoryFactoryBeanSupport}, registering the corresponding entity class as a
40+
* {@link org.springframework.aot.hint.TypeHint} should any if that repository's method
41+
* use {@link AuthorizeReturnObject}.
42+
*
43+
* <p>
44+
* It also traverses those found types for other return values.
45+
*
46+
* <p>
47+
* An instance of this class is published as an infrastructural bean by the
48+
* {@code spring-security-config} module. However, in the event you need to publish it
49+
* yourself, remember to publish it as an infrastructural bean like so:
50+
*
51+
* <pre>
52+
* &#064;Bean
53+
* &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
54+
* static SecurityHintsRegistrar proxyThese(AuthorizationProxyFactory proxyFactory) {
55+
* return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
56+
* }
57+
* </pre>
58+
*
59+
* @author Josh Cummings
60+
* @since 6.4
61+
* @see AuthorizeReturnObjectCoreHintsRegistrar
62+
* @see AuthorizeReturnObjectHintsRegistrar
63+
*/
64+
public final class AuthorizeReturnObjectDataHintsRegistrar implements SecurityHintsRegistrar {
65+
66+
private final AuthorizationProxyFactory proxyFactory;
67+
68+
private final SecurityAnnotationScanner<AuthorizeReturnObject> scanner = SecurityAnnotationScanners
69+
.requireUnique(AuthorizeReturnObject.class);
70+
71+
private final Set<Class<?>> visitedClasses = new HashSet<>();
72+
73+
public AuthorizeReturnObjectDataHintsRegistrar(AuthorizationProxyFactory proxyFactory) {
74+
this.proxyFactory = proxyFactory;
75+
}
76+
77+
@Override
78+
public void registerHints(RuntimeHints hints, ConfigurableListableBeanFactory beanFactory) {
79+
List<Class<?>> toProxy = new ArrayList<>();
80+
for (String name : beanFactory.getBeanDefinitionNames()) {
81+
ResolvableType type = beanFactory.getBeanDefinition(name).getResolvableType();
82+
if (!RepositoryFactoryBeanSupport.class.isAssignableFrom(type.toClass())) {
83+
continue;
84+
}
85+
Class<?>[] generics = type.resolveGenerics();
86+
Class<?> entity = generics[1];
87+
AuthorizeReturnObject authorize = beanFactory.findAnnotationOnBean(name, AuthorizeReturnObject.class);
88+
if (authorize != null) {
89+
toProxy.add(entity);
90+
continue;
91+
}
92+
Class<?> repository = generics[0];
93+
for (Method method : repository.getDeclaredMethods()) {
94+
AuthorizeReturnObject returnObject = this.scanner.scan(method, repository);
95+
if (returnObject == null) {
96+
continue;
97+
}
98+
// optimistically assume that the entity needs wrapping if any of the
99+
// repository methods use @AuthorizeReturnObject
100+
toProxy.add(entity);
101+
break;
102+
}
103+
}
104+
new AuthorizeReturnObjectHintsRegistrar(this.proxyFactory, toProxy).registerHints(hints, beanFactory);
105+
}
106+
107+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.data.aot.hint;
18+
19+
import java.util.List;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.aot.hint.RuntimeHints;
24+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
25+
import org.springframework.context.annotation.Bean;
26+
import org.springframework.context.annotation.Configuration;
27+
import org.springframework.context.support.GenericApplicationContext;
28+
import org.springframework.data.repository.CrudRepository;
29+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
30+
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
31+
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
32+
import org.springframework.security.authorization.AuthorizationProxyFactory;
33+
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
34+
import org.springframework.security.authorization.method.AuthorizeReturnObject;
35+
36+
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.mockito.Mockito.mock;
38+
import static org.mockito.Mockito.spy;
39+
40+
/**
41+
* Tests for {@link AuthorizeReturnObjectDataHintsRegistrar}
42+
*/
43+
public class AuthorizeReturnObjectDataHintsRegistrarTests {
44+
45+
private final AuthorizationProxyFactory proxyFactory = spy(AuthorizationAdvisorProxyFactory.withDefaults());
46+
47+
private final SecurityHintsRegistrar registrar = new AuthorizeReturnObjectDataHintsRegistrar(this.proxyFactory);
48+
49+
@Test
50+
public void registerHintsWhenUsingAuthorizeReturnObjectThenRegisters() {
51+
GenericApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
52+
RuntimeHints hints = new RuntimeHints();
53+
this.registrar.registerHints(hints, context.getBeanFactory());
54+
assertThat(hints.reflection().typeHints().map((hint) -> hint.getType().getName()))
55+
.containsOnly(cglibClassName(MyObject.class), cglibClassName(MySubObject.class));
56+
}
57+
58+
private static String cglibClassName(Class<?> clazz) {
59+
return clazz.getName() + "$$SpringCGLIB$$0";
60+
}
61+
62+
@AuthorizeReturnObject
63+
public interface MyInterface extends CrudRepository<MyObject, Long> {
64+
65+
List<MyObject> findAll();
66+
67+
}
68+
69+
public static class MyObject {
70+
71+
@AuthorizeReturnObject
72+
public MySubObject get() {
73+
return new MySubObject();
74+
}
75+
76+
}
77+
78+
public static class MySubObject {
79+
80+
}
81+
82+
@Configuration
83+
static class AppConfig {
84+
85+
@Bean
86+
RepositoryFactoryBeanSupport<MyInterface, MyObject, Long> bean() {
87+
return new RepositoryFactoryBeanSupport<>(MyInterface.class) {
88+
@Override
89+
public MyInterface getObject() {
90+
return mock(MyInterface.class);
91+
}
92+
93+
@Override
94+
public Class<? extends MyInterface> getObjectType() {
95+
return MyInterface.class;
96+
}
97+
98+
@Override
99+
public void afterPropertiesSet() {
100+
}
101+
102+
@Override
103+
protected RepositoryFactorySupport createRepositoryFactory() {
104+
return null;
105+
}
106+
};
107+
}
108+
109+
}
110+
111+
}

0 commit comments

Comments
 (0)