Skip to content

Commit

Permalink
Add AuthorizeReturnObject Spring Data Hints
Browse files Browse the repository at this point in the history
  • Loading branch information
jzheaux committed Sep 9, 2024
1 parent 033d09b commit 5339eb4
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 0 deletions.
1 change: 1 addition & 0 deletions config/spring-security-config.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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}
Expand All @@ -37,6 +38,9 @@
*/
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
Expand All @@ -57,6 +61,9 @@ public String[] selectImports(@NonNull AnnotationMetadata importMetadata) {
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
}
imports.add(AuthorizationProxyConfiguration.class.getName());
if (isDataPresent) {
imports.add(AuthorizationProxyDataConfiguration.class.getName());
}
return imports.toArray(new String[0]);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
};
}

}

}

0 comments on commit 5339eb4

Please sign in to comment.