Skip to content

Commit 78b97d3

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

File tree

2 files changed

+145
-1
lines changed

2 files changed

+145
-1
lines changed

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

Lines changed: 46 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,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,39 @@ static SecurityHintsRegistrar authorizeReturnObjectDataHintsRegistrar(Authorizat
3444
return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
3545
}
3646

47+
@Bean
48+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
49+
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
50+
DataTargetVisitor dataTargetVisitor() {
51+
return new DataTargetVisitor();
52+
}
53+
54+
private static final 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<>(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+
}
81+
3782
}

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

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@
6464
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
6565
import org.springframework.core.annotation.AnnotationConfigurationException;
6666
import org.springframework.core.annotation.Order;
67+
import org.springframework.data.domain.Page;
68+
import org.springframework.data.domain.PageImpl;
69+
import org.springframework.data.domain.Slice;
70+
import org.springframework.data.domain.SliceImpl;
71+
import org.springframework.data.geo.Distance;
72+
import org.springframework.data.geo.GeoPage;
73+
import org.springframework.data.geo.GeoResult;
74+
import org.springframework.data.geo.GeoResults;
6775
import org.springframework.http.HttpStatus;
6876
import org.springframework.http.HttpStatusCode;
6977
import org.springframework.http.MediaType;
@@ -756,6 +764,28 @@ public void findByIdWhenUnauthorizedResultThenDenies() {
756764
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude);
757765
}
758766

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

860+
@Test
861+
@WithMockUser(authorities = "airplane:read")
862+
public void findPageWhenPostFilterThenFilters() {
863+
this.spring.register(AuthorizeResultConfig.class).autowire();
864+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
865+
flights.findPage()
866+
.forEach((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName)
867+
.doesNotContain("Kevin Mitnick"));
868+
}
869+
870+
@Test
871+
@WithMockUser(authorities = "airplane:read")
872+
public void findSliceWhenPostFilterThenFilters() {
873+
this.spring.register(AuthorizeResultConfig.class).autowire();
874+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
875+
flights.findSlice()
876+
.forEach((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName)
877+
.doesNotContain("Kevin Mitnick"));
878+
}
879+
880+
@Test
881+
@WithMockUser(authorities = "airplane:read")
882+
public void findGeoPageWhenPostFilterThenFilters() {
883+
this.spring.register(AuthorizeResultConfig.class).autowire();
884+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
885+
flights.findGeoPage()
886+
.forEach((flight) -> assertThat(flight.getContent().getPassengers()).extracting(Passenger::getName)
887+
.doesNotContain("Kevin Mitnick"));
888+
}
889+
890+
@Test
891+
@WithMockUser(authorities = "airplane:read")
892+
public void findGeoResultsWhenPostFilterThenFilters() {
893+
this.spring.register(AuthorizeResultConfig.class).autowire();
894+
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
895+
flights.findGeoResults()
896+
.forEach((flight) -> assertThat(flight.getContent().getPassengers()).extracting(Passenger::getName)
897+
.doesNotContain("Kevin Mitnick"));
898+
}
899+
830900
@Test
831901
@WithMockUser(authorities = "airplane:read")
832902
public void findAllWhenPreFilterThenFilters() {
@@ -1802,10 +1872,39 @@ Iterator<Flight> findAll() {
18021872
return this.flights.values().iterator();
18031873
}
18041874

1875+
Page<Flight> findPage() {
1876+
return new PageImpl<>(new ArrayList<>(this.flights.values()));
1877+
}
1878+
1879+
Slice<Flight> findSlice() {
1880+
return new SliceImpl<>(new ArrayList<>(this.flights.values()));
1881+
}
1882+
1883+
GeoPage<Flight> findGeoPage() {
1884+
List<GeoResult<Flight>> results = new ArrayList<>();
1885+
for (Flight flight : this.flights.values()) {
1886+
results.add(new GeoResult<>(flight, new Distance(flight.altitude)));
1887+
}
1888+
return new GeoPage<>(new GeoResults<>(results));
1889+
}
1890+
1891+
GeoResults<Flight> findGeoResults() {
1892+
List<GeoResult<Flight>> results = new ArrayList<>();
1893+
for (Flight flight : this.flights.values()) {
1894+
results.add(new GeoResult<>(flight, new Distance(flight.altitude)));
1895+
}
1896+
return new GeoResults<>(results);
1897+
}
1898+
18051899
Flight findById(String id) {
18061900
return this.flights.get(id);
18071901
}
18081902

1903+
GeoResult<Flight> findGeoResultFlightById(String id) {
1904+
Flight flight = this.flights.get(id);
1905+
return new GeoResult<>(flight, new Distance(flight.altitude));
1906+
}
1907+
18091908
Flight save(Flight flight) {
18101909
this.flights.put(flight.getId(), flight);
18111910
return flight;

0 commit comments

Comments
 (0)