Преглед изворни кода

Add AuthorizeReturnObject Spring Data Hints

Issue gh-15709
Josh Cummings пре 11 месеци
родитељ
комит
e29058c7e4

+ 1 - 0
config/spring-security-config.gradle

@@ -21,6 +21,7 @@ dependencies {
 	api 'org.springframework:spring-context'
 	api 'org.springframework:spring-core'
 
+	optional project(':spring-security-data')
 	optional project(':spring-security-ldap')
 	optional project(':spring-security-messaging')
 	optional project(path: ':spring-security-saml2-service-provider')

+ 37 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyDataConfiguration.java

@@ -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.config.annotation.method.configuration;
+
+import org.springframework.aop.framework.AopInfrastructureBean;
+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.security.aot.hint.SecurityHintsRegistrar;
+import org.springframework.security.authorization.AuthorizationProxyFactory;
+import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar;
+
+@Configuration(proxyBeanMethods = false)
+final class AuthorizationProxyDataConfiguration implements AopInfrastructureBean {
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static SecurityHintsRegistrar authorizeReturnObjectDataHintsRegistrar(AuthorizationProxyFactory proxyFactory) {
+		return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
+	}
+
+}

+ 7 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java

@@ -26,6 +26,7 @@ import org.springframework.context.annotation.AutoProxyRegistrar;
 import org.springframework.context.annotation.ImportSelector;
 import org.springframework.core.type.AnnotationMetadata;
 import org.springframework.lang.NonNull;
+import org.springframework.util.ClassUtils;
 
 /**
  * Dynamically determines which imports to include using the {@link EnableMethodSecurity}
@@ -37,6 +38,9 @@ import org.springframework.lang.NonNull;
  */
 final class MethodSecuritySelector implements ImportSelector {
 
+	private static final boolean isDataPresent = ClassUtils
+		.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
+
 	private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
 
 	@Override
@@ -57,6 +61,9 @@ final class MethodSecuritySelector implements ImportSelector {
 			imports.add(Jsr250MethodSecurityConfiguration.class.getName());
 		}
 		imports.add(AuthorizationProxyConfiguration.class.getName());
+		if (isDataPresent) {
+			imports.add(AuthorizationProxyDataConfiguration.class.getName());
+		}
 		return imports.toArray(new String[0]);
 	}
 

+ 107 - 0
data/src/main/java/org/springframework/security/data/aot/hint/AuthorizeReturnObjectDataHintsRegistrar.java

@@ -0,0 +1,107 @@
+/*
+ * 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.data.aot.hint;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.core.ResolvableType;
+import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
+import org.springframework.security.aot.hint.AuthorizeReturnObjectCoreHintsRegistrar;
+import org.springframework.security.aot.hint.AuthorizeReturnObjectHintsRegistrar;
+import org.springframework.security.aot.hint.SecurityHintsRegistrar;
+import org.springframework.security.authorization.AuthorizationProxyFactory;
+import org.springframework.security.authorization.method.AuthorizeReturnObject;
+import org.springframework.security.core.annotation.SecurityAnnotationScanner;
+import org.springframework.security.core.annotation.SecurityAnnotationScanners;
+
+/**
+ * A {@link SecurityHintsRegistrar} that scans all beans for implementations of
+ * {@link RepositoryFactoryBeanSupport}, registering the corresponding entity class as a
+ * {@link org.springframework.aot.hint.TypeHint} should any if that repository's method
+ * use {@link AuthorizeReturnObject}.
+ *
+ * <p>
+ * It also traverses those found types for other return values.
+ *
+ * <p>
+ * An instance of this class is published as an infrastructural bean by the
+ * {@code spring-security-config} module. However, in the event you need to publish it
+ * yourself, remember to publish it as an infrastructural bean like so:
+ *
+ * <pre>
+ *	&#064;Bean
+ *	&#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ *	static SecurityHintsRegistrar proxyThese(AuthorizationProxyFactory proxyFactory) {
+ *		return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
+ *	}
+ * </pre>
+ *
+ * @author Josh Cummings
+ * @since 6.4
+ * @see AuthorizeReturnObjectCoreHintsRegistrar
+ * @see AuthorizeReturnObjectHintsRegistrar
+ */
+public final class AuthorizeReturnObjectDataHintsRegistrar implements SecurityHintsRegistrar {
+
+	private final AuthorizationProxyFactory proxyFactory;
+
+	private final SecurityAnnotationScanner<AuthorizeReturnObject> scanner = SecurityAnnotationScanners
+		.requireUnique(AuthorizeReturnObject.class);
+
+	private final Set<Class<?>> visitedClasses = new HashSet<>();
+
+	public AuthorizeReturnObjectDataHintsRegistrar(AuthorizationProxyFactory proxyFactory) {
+		this.proxyFactory = proxyFactory;
+	}
+
+	@Override
+	public void registerHints(RuntimeHints hints, ConfigurableListableBeanFactory beanFactory) {
+		List<Class<?>> toProxy = new ArrayList<>();
+		for (String name : beanFactory.getBeanDefinitionNames()) {
+			ResolvableType type = beanFactory.getBeanDefinition(name).getResolvableType();
+			if (!RepositoryFactoryBeanSupport.class.isAssignableFrom(type.toClass())) {
+				continue;
+			}
+			Class<?>[] generics = type.resolveGenerics();
+			Class<?> entity = generics[1];
+			AuthorizeReturnObject authorize = beanFactory.findAnnotationOnBean(name, AuthorizeReturnObject.class);
+			if (authorize != null) {
+				toProxy.add(entity);
+				continue;
+			}
+			Class<?> repository = generics[0];
+			for (Method method : repository.getDeclaredMethods()) {
+				AuthorizeReturnObject returnObject = this.scanner.scan(method, repository);
+				if (returnObject == null) {
+					continue;
+				}
+				// optimistically assume that the entity needs wrapping if any of the
+				// repository methods use @AuthorizeReturnObject
+				toProxy.add(entity);
+				break;
+			}
+		}
+		new AuthorizeReturnObjectHintsRegistrar(this.proxyFactory, toProxy).registerHints(hints, beanFactory);
+	}
+
+}

+ 111 - 0
data/src/test/java/org/springframework/security/data/aot/hint/AuthorizeReturnObjectDataHintsRegistrarTests.java

@@ -0,0 +1,111 @@
+/*
+ * 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.data.aot.hint;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
+import org.springframework.data.repository.core.support.RepositoryFactorySupport;
+import org.springframework.security.aot.hint.SecurityHintsRegistrar;
+import org.springframework.security.authorization.AuthorizationProxyFactory;
+import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
+import org.springframework.security.authorization.method.AuthorizeReturnObject;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+/**
+ * Tests for {@link AuthorizeReturnObjectDataHintsRegistrar}
+ */
+public class AuthorizeReturnObjectDataHintsRegistrarTests {
+
+	private final AuthorizationProxyFactory proxyFactory = spy(AuthorizationAdvisorProxyFactory.withDefaults());
+
+	private final SecurityHintsRegistrar registrar = new AuthorizeReturnObjectDataHintsRegistrar(this.proxyFactory);
+
+	@Test
+	public void registerHintsWhenUsingAuthorizeReturnObjectThenRegisters() {
+		GenericApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
+		RuntimeHints hints = new RuntimeHints();
+		this.registrar.registerHints(hints, context.getBeanFactory());
+		assertThat(hints.reflection().typeHints().map((hint) -> hint.getType().getName()))
+			.containsOnly(cglibClassName(MyObject.class), cglibClassName(MySubObject.class));
+	}
+
+	private static String cglibClassName(Class<?> clazz) {
+		return clazz.getName() + "$$SpringCGLIB$$0";
+	}
+
+	@AuthorizeReturnObject
+	public interface MyInterface extends CrudRepository<MyObject, Long> {
+
+		List<MyObject> findAll();
+
+	}
+
+	public static class MyObject {
+
+		@AuthorizeReturnObject
+		public MySubObject get() {
+			return new MySubObject();
+		}
+
+	}
+
+	public static class MySubObject {
+
+	}
+
+	@Configuration
+	static class AppConfig {
+
+		@Bean
+		RepositoryFactoryBeanSupport<MyInterface, MyObject, Long> bean() {
+			return new RepositoryFactoryBeanSupport<>(MyInterface.class) {
+				@Override
+				public MyInterface getObject() {
+					return mock(MyInterface.class);
+				}
+
+				@Override
+				public Class<? extends MyInterface> getObjectType() {
+					return MyInterface.class;
+				}
+
+				@Override
+				public void afterPropertiesSet() {
+				}
+
+				@Override
+				protected RepositoryFactorySupport createRepositoryFactory() {
+					return null;
+				}
+			};
+		}
+
+	}
+
+}