Skip to content

Commit dc1a191

Browse files
committed
Consider supporting Spring Data container types for AuthorizeReturnObject
Closes spring-projectsgh-15994 Signed-off-by: Evgeniy Cheban <[email protected]>
1 parent ef4479a commit dc1a191

File tree

2 files changed

+151
-1
lines changed

2 files changed

+151
-1
lines changed

Diff for: config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyDataConfiguration.java

+52-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,13 +16,23 @@
1616

1717
package org.springframework.security.config.annotation.method.configuration;
1818

19+
import java.util.List;
20+
1921
import org.springframework.aop.framework.AopInfrastructureBean;
2022
import org.springframework.beans.factory.config.BeanDefinition;
2123
import org.springframework.context.annotation.Bean;
2224
import org.springframework.context.annotation.Configuration;
2325
import org.springframework.context.annotation.Role;
26+
import org.springframework.core.Ordered;
27+
import org.springframework.core.annotation.Order;
28+
import org.springframework.data.domain.PageImpl;
29+
import org.springframework.data.domain.SliceImpl;
30+
import org.springframework.data.geo.GeoPage;
31+
import org.springframework.data.geo.GeoResult;
32+
import org.springframework.data.geo.GeoResults;
2433
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
2534
import org.springframework.security.authorization.AuthorizationProxyFactory;
35+
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
2636
import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar;
2737

2838
@Configuration(proxyBeanMethods = false)
@@ -34,4 +44,45 @@ static SecurityHintsRegistrar authorizeReturnObjectDataHintsRegistrar(Authorizat
3444
return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
3545
}
3646

47+
@Bean
48+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
49+
@Order(Ordered.HIGHEST_PRECEDENCE)
50+
DataTargetVisitor dataTargetVisitor() {
51+
return new DataTargetVisitor();
52+
}
53+
54+
static class DataTargetVisitor implements AuthorizationAdvisorProxyFactory.TargetVisitor {
55+
56+
@Override
57+
public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) {
58+
if (target instanceof GeoResults<?> geoResults) {
59+
return new GeoResults<>(proxyCast(proxyFactory, geoResults.getContent()),
60+
geoResults.getAverageDistance());
61+
}
62+
if (target instanceof GeoResult<?> geoResult) {
63+
return new GeoResult<>(proxyCast(proxyFactory, geoResult.getContent()), geoResult.getDistance());
64+
}
65+
if (target instanceof GeoPage<?> geoPage) {
66+
GeoResults<?> results = new GeoResults<>(proxyCast(proxyFactory, geoPage.getContent()),
67+
geoPage.getAverageDistance());
68+
return new GeoPage<>(results, geoPage.getPageable(), geoPage.getTotalElements());
69+
}
70+
if (target instanceof PageImpl<?> page) {
71+
List<?> content = proxyCast(proxyFactory, page.getContent());
72+
return new PageImpl<>(content, page.getPageable(), page.getTotalElements());
73+
}
74+
if (target instanceof SliceImpl<?> slice) {
75+
List<?> content = proxyCast(proxyFactory, slice.getContent());
76+
return new SliceImpl<>(content, slice.getPageable(), slice.hasNext());
77+
}
78+
return null;
79+
}
80+
81+
@SuppressWarnings("unchecked")
82+
private <T> T proxyCast(AuthorizationAdvisorProxyFactory proxyFactory, T target) {
83+
return (T) proxyFactory.proxy(target);
84+
}
85+
86+
}
87+
3788
}

Diff for: config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

+99
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@
6262
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
6363
import org.springframework.core.annotation.AnnotationConfigurationException;
6464
import org.springframework.core.annotation.Order;
65+
import org.springframework.data.domain.Page;
66+
import org.springframework.data.domain.PageImpl;
67+
import org.springframework.data.domain.Slice;
68+
import org.springframework.data.domain.SliceImpl;
69+
import org.springframework.data.geo.Distance;
70+
import org.springframework.data.geo.GeoPage;
71+
import org.springframework.data.geo.GeoResult;
72+
import org.springframework.data.geo.GeoResults;
6573
import org.springframework.http.HttpStatusCode;
6674
import org.springframework.http.ResponseEntity;
6775
import org.springframework.security.access.AccessDeniedException;
@@ -733,6 +741,28 @@ public void findByIdWhenUnauthorizedResultThenDenies() {
733741
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude);
734742
}
735743

744+
@Test
745+
@WithMockUser(authorities = "airplane:read")
746+
public void findGeoResultByIdWhenAuthorizedResultThenAuthorizes() {
747+
this.spring.register(AuthorizeResultConfig.class).autowire();
748+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
749+
GeoResult<Flight> geoResultFlight = flights.findGeoResultFlightById("1");
750+
Flight flight = geoResultFlight.getContent();
751+
assertThatNoException().isThrownBy(flight::getAltitude);
752+
assertThatNoException().isThrownBy(flight::getSeats);
753+
}
754+
755+
@Test
756+
@WithMockUser(authorities = "seating:read")
757+
public void findGeoResultByIdWhenUnauthorizedResultThenDenies() {
758+
this.spring.register(AuthorizeResultConfig.class).autowire();
759+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
760+
GeoResult<Flight> geoResultFlight = flights.findGeoResultFlightById("1");
761+
Flight flight = geoResultFlight.getContent();
762+
assertThatNoException().isThrownBy(flight::getSeats);
763+
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude);
764+
}
765+
736766
@Test
737767
@WithMockUser(authorities = "airplane:read")
738768
public void findByIdWhenAuthorizedResponseEntityThenAuthorizes() {
@@ -804,6 +834,46 @@ public void findAllWhenPostFilterThenFilters() {
804834
.doesNotContain("Kevin Mitnick"));
805835
}
806836

837+
@Test
838+
@WithMockUser(authorities = "airplane:read")
839+
public void findPageWhenPostFilterThenFilters() {
840+
this.spring.register(AuthorizeResultConfig.class).autowire();
841+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
842+
flights.findPage()
843+
.forEach((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName)
844+
.doesNotContain("Kevin Mitnick"));
845+
}
846+
847+
@Test
848+
@WithMockUser(authorities = "airplane:read")
849+
public void findSliceWhenPostFilterThenFilters() {
850+
this.spring.register(AuthorizeResultConfig.class).autowire();
851+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
852+
flights.findSlice()
853+
.forEach((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName)
854+
.doesNotContain("Kevin Mitnick"));
855+
}
856+
857+
@Test
858+
@WithMockUser(authorities = "airplane:read")
859+
public void findGeoPageWhenPostFilterThenFilters() {
860+
this.spring.register(AuthorizeResultConfig.class).autowire();
861+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
862+
flights.findGeoPage()
863+
.forEach((flight) -> assertThat(flight.getContent().getPassengers()).extracting(Passenger::getName)
864+
.doesNotContain("Kevin Mitnick"));
865+
}
866+
867+
@Test
868+
@WithMockUser(authorities = "airplane:read")
869+
public void findGeoResultsWhenPostFilterThenFilters() {
870+
this.spring.register(AuthorizeResultConfig.class).autowire();
871+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
872+
flights.findGeoResults()
873+
.forEach((flight) -> assertThat(flight.getContent().getPassengers()).extracting(Passenger::getName)
874+
.doesNotContain("Kevin Mitnick"));
875+
}
876+
807877
@Test
808878
@WithMockUser(authorities = "airplane:read")
809879
public void findAllWhenPreFilterThenFilters() {
@@ -1688,10 +1758,39 @@ Iterator<Flight> findAll() {
16881758
return this.flights.values().iterator();
16891759
}
16901760

1761+
Page<Flight> findPage() {
1762+
return new PageImpl<>(new ArrayList<>(this.flights.values()));
1763+
}
1764+
1765+
Slice<Flight> findSlice() {
1766+
return new SliceImpl<>(new ArrayList<>(this.flights.values()));
1767+
}
1768+
1769+
GeoPage<Flight> findGeoPage() {
1770+
List<GeoResult<Flight>> results = new ArrayList<>();
1771+
for (Flight flight : this.flights.values()) {
1772+
results.add(new GeoResult<>(flight, new Distance(flight.altitude)));
1773+
}
1774+
return new GeoPage<>(new GeoResults<>(results));
1775+
}
1776+
1777+
GeoResults<Flight> findGeoResults() {
1778+
List<GeoResult<Flight>> results = new ArrayList<>();
1779+
for (Flight flight : this.flights.values()) {
1780+
results.add(new GeoResult<>(flight, new Distance(flight.altitude)));
1781+
}
1782+
return new GeoResults<>(results);
1783+
}
1784+
16911785
Flight findById(String id) {
16921786
return this.flights.get(id);
16931787
}
16941788

1789+
GeoResult<Flight> findGeoResultFlightById(String id) {
1790+
Flight flight = this.flights.get(id);
1791+
return new GeoResult<>(flight, new Distance(flight.altitude));
1792+
}
1793+
16951794
Flight save(Flight flight) {
16961795
this.flights.put(flight.getId(), flight);
16971796
return flight;

0 commit comments

Comments
 (0)