Skip to content

Commit 318a463

Browse files
committed
Support Spring Data container types for AuthorizeReturnObject
Closes gh-15994 Signed-off-by: Evgeniy Cheban <[email protected]>
1 parent 6d3b54d commit 318a463

File tree

2 files changed

+152
-12
lines changed

2 files changed

+152
-12
lines changed

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

Lines changed: 51 additions & 1 deletion
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,22 @@
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.data.domain.PageImpl;
28+
import org.springframework.data.domain.SliceImpl;
29+
import org.springframework.data.geo.GeoPage;
30+
import org.springframework.data.geo.GeoResult;
31+
import org.springframework.data.geo.GeoResults;
2432
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
2533
import org.springframework.security.authorization.AuthorizationProxyFactory;
34+
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
2635
import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar;
2736

2837
@Configuration(proxyBeanMethods = false)
@@ -34,4 +43,45 @@ static SecurityHintsRegistrar authorizeReturnObjectDataHintsRegistrar(Authorizat
3443
return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
3544
}
3645

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

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

Lines changed: 101 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,14 @@
6363
import org.springframework.context.event.EventListener;
6464
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
6565
import org.springframework.core.annotation.AnnotationConfigurationException;
66-
import org.springframework.core.annotation.Order;
66+
import org.springframework.data.domain.Page;
67+
import org.springframework.data.domain.PageImpl;
68+
import org.springframework.data.domain.Slice;
69+
import org.springframework.data.domain.SliceImpl;
70+
import org.springframework.data.geo.Distance;
71+
import org.springframework.data.geo.GeoPage;
72+
import org.springframework.data.geo.GeoResult;
73+
import org.springframework.data.geo.GeoResults;
6774
import org.springframework.http.HttpStatus;
6875
import org.springframework.http.HttpStatusCode;
6976
import org.springframework.http.MediaType;
@@ -756,6 +763,28 @@ public void findByIdWhenUnauthorizedResultThenDenies() {
756763
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude);
757764
}
758765

766+
@Test
767+
@WithMockUser(authorities = "airplane:read")
768+
public void findGeoResultByIdWhenAuthorizedResultThenAuthorizes() {
769+
this.spring.register(AuthorizeResultConfig.class).autowire();
770+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
771+
GeoResult<Flight> geoResultFlight = flights.findGeoResultFlightById("1");
772+
Flight flight = geoResultFlight.getContent();
773+
assertThatNoException().isThrownBy(flight::getAltitude);
774+
assertThatNoException().isThrownBy(flight::getSeats);
775+
}
776+
777+
@Test
778+
@WithMockUser(authorities = "seating:read")
779+
public void findGeoResultByIdWhenUnauthorizedResultThenDenies() {
780+
this.spring.register(AuthorizeResultConfig.class).autowire();
781+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
782+
GeoResult<Flight> geoResultFlight = flights.findGeoResultFlightById("1");
783+
Flight flight = geoResultFlight.getContent();
784+
assertThatNoException().isThrownBy(flight::getSeats);
785+
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude);
786+
}
787+
759788
@Test
760789
@WithMockUser(authorities = "airplane:read")
761790
public void findByIdWhenAuthorizedResponseEntityThenAuthorizes() {
@@ -827,6 +856,46 @@ public void findAllWhenPostFilterThenFilters() {
827856
.doesNotContain("Kevin Mitnick"));
828857
}
829858

859+
@Test
860+
@WithMockUser(authorities = "airplane:read")
861+
public void findPageWhenPostFilterThenFilters() {
862+
this.spring.register(AuthorizeResultConfig.class).autowire();
863+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
864+
flights.findPage()
865+
.forEach((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName)
866+
.doesNotContain("Kevin Mitnick"));
867+
}
868+
869+
@Test
870+
@WithMockUser(authorities = "airplane:read")
871+
public void findSliceWhenPostFilterThenFilters() {
872+
this.spring.register(AuthorizeResultConfig.class).autowire();
873+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
874+
flights.findSlice()
875+
.forEach((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName)
876+
.doesNotContain("Kevin Mitnick"));
877+
}
878+
879+
@Test
880+
@WithMockUser(authorities = "airplane:read")
881+
public void findGeoPageWhenPostFilterThenFilters() {
882+
this.spring.register(AuthorizeResultConfig.class).autowire();
883+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
884+
flights.findGeoPage()
885+
.forEach((flight) -> assertThat(flight.getContent().getPassengers()).extracting(Passenger::getName)
886+
.doesNotContain("Kevin Mitnick"));
887+
}
888+
889+
@Test
890+
@WithMockUser(authorities = "airplane:read")
891+
public void findGeoResultsWhenPostFilterThenFilters() {
892+
this.spring.register(AuthorizeResultConfig.class).autowire();
893+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
894+
flights.findGeoResults()
895+
.forEach((flight) -> assertThat(flight.getContent().getPassengers()).extracting(Passenger::getName)
896+
.doesNotContain("Kevin Mitnick"));
897+
}
898+
830899
@Test
831900
@WithMockUser(authorities = "airplane:read")
832901
public void findAllWhenPreFilterThenFilters() {
@@ -1762,16 +1831,8 @@ static class AuthorizeResultConfig {
17621831

17631832
@Bean
17641833
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
1765-
@Order(1)
1766-
static TargetVisitor mock() {
1767-
return Mockito.mock(TargetVisitor.class);
1768-
}
1769-
1770-
@Bean
1771-
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
1772-
@Order(0)
1773-
static TargetVisitor skipValueTypes() {
1774-
return TargetVisitor.defaultsSkipValueTypes();
1834+
static TargetVisitor customTargetVisitor() {
1835+
return TargetVisitor.of(Mockito.mock(), TargetVisitor.defaultsSkipValueTypes());
17751836
}
17761837

17771838
@Bean
@@ -1802,10 +1863,39 @@ Iterator<Flight> findAll() {
18021863
return this.flights.values().iterator();
18031864
}
18041865

1866+
Page<Flight> findPage() {
1867+
return new PageImpl<>(new ArrayList<>(this.flights.values()));
1868+
}
1869+
1870+
Slice<Flight> findSlice() {
1871+
return new SliceImpl<>(new ArrayList<>(this.flights.values()));
1872+
}
1873+
1874+
GeoPage<Flight> findGeoPage() {
1875+
List<GeoResult<Flight>> results = new ArrayList<>();
1876+
for (Flight flight : this.flights.values()) {
1877+
results.add(new GeoResult<>(flight, new Distance(flight.altitude)));
1878+
}
1879+
return new GeoPage<>(new GeoResults<>(results));
1880+
}
1881+
1882+
GeoResults<Flight> findGeoResults() {
1883+
List<GeoResult<Flight>> results = new ArrayList<>();
1884+
for (Flight flight : this.flights.values()) {
1885+
results.add(new GeoResult<>(flight, new Distance(flight.altitude)));
1886+
}
1887+
return new GeoResults<>(results);
1888+
}
1889+
18051890
Flight findById(String id) {
18061891
return this.flights.get(id);
18071892
}
18081893

1894+
GeoResult<Flight> findGeoResultFlightById(String id) {
1895+
Flight flight = this.flights.get(id);
1896+
return new GeoResult<>(flight, new Distance(flight.altitude));
1897+
}
1898+
18091899
Flight save(Flight flight) {
18101900
this.flights.put(flight.getId(), flight);
18111901
return flight;

0 commit comments

Comments
 (0)