Skip to content

Commit 5f9ea71

Browse files
authored
Merge pull request #376 from tmfg/feature/ext-authorization-municipalities
Fintraffic Feature/ext authorization municipalities
2 parents ecef5ca + a0f6558 commit 5f9ea71

9 files changed

Lines changed: 363 additions & 66 deletions

File tree

src/ext-test/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficAuthorizationServiceTest.java

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.locationtech.jts.geom.Coordinate;
55
import org.locationtech.jts.geom.GeometryFactory;
66
import org.locationtech.jts.geom.LinearRing;
7+
import org.locationtech.jts.geom.MultiPolygon;
78
import org.locationtech.jts.geom.Point;
89
import org.locationtech.jts.geom.Polygon;
910
import org.rutebanken.tiamat.exporter.params.TopographicPlaceSearch;
@@ -52,6 +53,18 @@ class FintrafficAuthorizationServiceTest {
5253
return place;
5354
}
5455

56+
private static @Nonnull TopographicPlace getMunicipalityTopographicPlace(String municipalityCode,
57+
Coordinate[] coordinates) {
58+
TopographicPlace place = new TopographicPlace();
59+
GeometryFactory fact = new GeometryFactory();
60+
LinearRing ring = fact.createLinearRing(coordinates);
61+
Polygon polygon = new Polygon(ring, null, fact);
62+
place.setMultiSurface(new MultiPolygon(new Polygon[]{polygon}, fact));
63+
place.setTopographicPlaceType(TopographicPlaceTypeEnumeration.MUNICIPALITY);
64+
place.setPrivateCode(new org.rutebanken.tiamat.model.PrivateCodeStructure(municipalityCode, "type"));
65+
return place;
66+
}
67+
5568
private static @Nonnull Point getPoint(Coordinate coordinate) {
5669
GeometryFactory fact = new GeometryFactory();
5770
return fact.createPoint(coordinate);
@@ -80,12 +93,25 @@ class FintrafficAuthorizationServiceTest {
8093
}
8194

8295
private static FintrafficAuthorizationService getAuthorizationService() {
96+
return getAuthorizationService(true, true);
97+
}
98+
99+
private static FintrafficAuthorizationService getAuthorizationService(boolean codespaceEnabled, boolean municipalityEnabled) {
83100
TopographicPlaceRepository topographicPlaceRepositoryMock = mock(TopographicPlaceRepository.class);
84101
TrivoreAuthorizations trivoreAuthorizationsMock = mock(TrivoreAuthorizations.class);
85102

86-
TopographicPlace place = getTopographicPlace();
87-
when(topographicPlaceRepositoryMock.findTopographicPlace(any(TopographicPlaceSearch.class))).thenReturn(List.of(place));
103+
TopographicPlace regionPlace = getTopographicPlace();
104+
TopographicPlace municipalityPlace = getMunicipalityTopographicPlace("091", new Coordinate[] {
105+
new Coordinate(2, 2),
106+
new Coordinate(3, 2),
107+
new Coordinate(3, 3),
108+
new Coordinate(2, 3),
109+
new Coordinate(2, 2),
110+
});
111+
when(topographicPlaceRepositoryMock.findTopographicPlace(any(TopographicPlaceSearch.class)))
112+
.thenReturn(List.of(regionPlace, municipalityPlace));
88113
when(trivoreAuthorizationsMock.getAccessibleCodespaces()).thenReturn(Set.of("ABC", "XYZ"));
114+
when(trivoreAuthorizationsMock.getAccessibleMunicipalityCodes()).thenReturn(Set.of("091"));
89115

90116
when(trivoreAuthorizationsMock.hasAccess(matches("StopPlace"), matches("BUS"), eq(TrivorePermission.MANAGE), anyBoolean())).thenReturn(true);
91117
when(trivoreAuthorizationsMock.hasAccess(matches("Parking"), matches("\\{all\\}"), eq(TrivorePermission.MANAGE), anyBoolean())).thenReturn(true);
@@ -94,7 +120,9 @@ private static FintrafficAuthorizationService getAuthorizationService() {
94120

95121
return new FintrafficAuthorizationService(
96122
trivoreAuthorizationsMock,
97-
topographicPlaceRepositoryMock
123+
topographicPlaceRepositoryMock,
124+
codespaceEnabled,
125+
municipalityEnabled
98126
);
99127
}
100128

@@ -153,4 +181,58 @@ public void testCanEditStopPlaceWithNestedEntitiesNotAllowed() {
153181
FintrafficAuthorizationService authorizationService = getAuthorizationService();
154182
assertThat(authorizationService.canEditEntity(stopPlaceWithQuayAndNestedStopPlace), equalTo(false));
155183
}
184+
185+
@Test
186+
public void testCanEditEntityByMunicipalityCodes() {
187+
// Point (2.5, 2.5) is inside municipality polygon (2,2)-(3,3) but outside region polygon (0,0)-(1,1)
188+
StopPlace stopPlace = getStopPlace("FSR:StopPlace:10", VehicleModeEnumeration.BUS, getPoint(new Coordinate(2.5, 2.5)));
189+
FintrafficAuthorizationService authorizationService = getAuthorizationService();
190+
assertThat(authorizationService.canEditEntity(stopPlace), equalTo(true));
191+
}
192+
193+
@Test
194+
public void testCanEditEntityByMunicipalityCodesOutOfBounds() {
195+
// Point (5, 5) is outside both region and municipality polygons
196+
StopPlace stopPlace = getStopPlace("FSR:StopPlace:11", VehicleModeEnumeration.BUS, getPoint(new Coordinate(5, 5)));
197+
FintrafficAuthorizationService authorizationService = getAuthorizationService();
198+
assertThat(authorizationService.canEditEntity(stopPlace), equalTo(false));
199+
}
200+
201+
@Test
202+
public void testCanEditEntityByEitherCodespaceOrMunicipality() {
203+
// Point (0.5, 0.5) is inside region polygon — should pass via codespace check
204+
StopPlace stopPlaceInRegion = getStopPlace("FSR:StopPlace:12", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.5, 0.5)));
205+
// Point (2.5, 2.5) is inside municipality polygon — should pass via municipality check
206+
StopPlace stopPlaceInMunicipality = getStopPlace("FSR:StopPlace:13", VehicleModeEnumeration.BUS, getPoint(new Coordinate(2.5, 2.5)));
207+
208+
FintrafficAuthorizationService authorizationService = getAuthorizationService();
209+
assertThat(authorizationService.canEditEntity(stopPlaceInRegion), equalTo(true));
210+
assertThat(authorizationService.canEditEntity(stopPlaceInMunicipality), equalTo(true));
211+
}
212+
213+
@Test
214+
public void testCodespaceOnlyMode() {
215+
FintrafficAuthorizationService authorizationService = getAuthorizationService(true, false);
216+
// Point inside region (codespace) — allowed
217+
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(0.5, 0.5))), equalTo(true));
218+
// Point inside municipality area but municipality auth disabled — denied
219+
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(2.5, 2.5))), equalTo(false));
220+
}
221+
222+
@Test
223+
public void testMunicipalityOnlyMode() {
224+
FintrafficAuthorizationService authorizationService = getAuthorizationService(false, true);
225+
// Point inside region but codespace auth disabled — denied
226+
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(0.5, 0.5))), equalTo(false));
227+
// Point inside municipality area — allowed
228+
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(2.5, 2.5))), equalTo(true));
229+
}
230+
231+
@Test
232+
public void testBothAuthorizationMethodsDisabled() {
233+
FintrafficAuthorizationService authorizationService = getAuthorizationService(false, false);
234+
// Both disabled — all geographic edits denied
235+
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(0.5, 0.5))), equalTo(false));
236+
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(2.5, 2.5))), equalTo(false));
237+
}
156238
}

src/ext-test/java/org/rutebanken/tiamat/ext/fintraffic/auth/TrivoreAuthorizationsTest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ void setUp() {
4545
case "/api/rest/v1/user/test-subject/groupmembership" -> Mono.just(
4646
ClientResponse.create(HttpStatus.OK)
4747
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
48-
.body("[{\"id\": \"test-id\",\"name\": \"group-name\",\"customFields\":{\"codespaces\": \"ABC,XYZ\"}}]")
48+
.body("[{\"id\": \"test-id\",\"name\": \"group-name\",\"customFields\":{\"codespaces\": \"ABC,XYZ\",\"municipalityCodes\": \"091,049\"}}]")
4949
.build()
5050
);
5151
case "/api/rest/v1/user/test-subject/externalpermissions" -> Mono.just(
@@ -108,6 +108,12 @@ void testGetAccessibleCodespaces() {
108108
assertThat(codespaces, equalTo(Set.of("ABC", "XYZ")));
109109
}
110110

111+
@Test
112+
void testGetAccessibleMunicipalityCodes() {
113+
Set<String> municipalityCodes = trivoreAuthorizations.getAccessibleMunicipalityCodes();
114+
assertThat(municipalityCodes, equalTo(Set.of("091", "049")));
115+
}
116+
111117
@Test
112118
void testHasAccess() {
113119
assertThat(trivoreAuthorizations.hasAccess("StopPlace", "bus", TrivorePermission.EDIT), equalTo(true));
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package org.rutebanken.tiamat.ext.fintraffic.auth.model;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.Map;
6+
import java.util.Set;
7+
8+
import static org.hamcrest.MatcherAssert.assertThat;
9+
import static org.hamcrest.Matchers.empty;
10+
import static org.hamcrest.Matchers.equalTo;
11+
12+
class GroupMembershipTest {
13+
14+
private static GroupMembership createMembership(String codespaces, String municipalityCodes) {
15+
Map<String, Object> customFields = Map.of(
16+
"codespaces", codespaces,
17+
"municipalityCodes", municipalityCodes
18+
);
19+
return new GroupMembership("g1", "Test Group", "desc", true, null, null, customFields);
20+
}
21+
22+
@Test
23+
void testValidCodespaces() {
24+
GroupMembership gm = createMembership("ABC,XYZ,ÅÄÖ", "");
25+
assertThat(gm.getCodespaces(), equalTo(Set.of("ABC", "XYZ", "ÅÄÖ")));
26+
}
27+
28+
@Test
29+
void testInvalidCodespacesSkipped() {
30+
GroupMembership gm = createMembership("abc,AB,1234", "");
31+
assertThat(gm.getCodespaces(), empty());
32+
}
33+
34+
@Test
35+
void testMixedCodespaces() {
36+
GroupMembership gm = createMembership("ABC, invalid, XYZ, ab", "");
37+
assertThat(gm.getCodespaces(), equalTo(Set.of("ABC", "XYZ")));
38+
}
39+
40+
@Test
41+
void testValidMunicipalityCodes() {
42+
GroupMembership gm = createMembership("", "091,049,1001");
43+
assertThat(gm.getMunicipalityCodes(), equalTo(Set.of("091", "049", "1001")));
44+
}
45+
46+
@Test
47+
void testInvalidMunicipalityCodesSkipped() {
48+
GroupMembership gm = createMembership("", "ABC,12,12345");
49+
assertThat(gm.getMunicipalityCodes(), empty());
50+
}
51+
52+
@Test
53+
void testMixedMunicipalityCodes() {
54+
GroupMembership gm = createMembership("", "091, bad, 049, ABCD");
55+
assertThat(gm.getMunicipalityCodes(), equalTo(Set.of("091", "049")));
56+
}
57+
58+
@Test
59+
void testNullCustomFields() {
60+
GroupMembership gm = new GroupMembership("g1", "Test", "desc", true, null, null, null);
61+
assertThat(gm.getCodespaces(), empty());
62+
assertThat(gm.getMunicipalityCodes(), empty());
63+
}
64+
65+
@Test
66+
void testEmptyValues() {
67+
GroupMembership gm = createMembership("", "");
68+
assertThat(gm.getCodespaces(), empty());
69+
assertThat(gm.getMunicipalityCodes(), empty());
70+
}
71+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.rutebanken.tiamat.ext.fintraffic;
2+
3+
public final class FintrafficConstants {
4+
5+
public static final String AREA_CODE_REGEX = "[A-ZÅÄÖ]{3}";
6+
// 3 digits for normal municipalities, 4 digits for special areas like Haaparanta and Eurooppa
7+
public static final String MUNICIPALITY_CODE_REGEX = "\\d{3,4}";
8+
9+
private FintrafficConstants() {}
10+
11+
public static boolean isValidAreaCode(String code) {
12+
return code != null && code.matches(AREA_CODE_REGEX);
13+
}
14+
15+
public static boolean isValidMunicipalityCode(String code) {
16+
return code != null && code.matches(MUNICIPALITY_CODE_REGEX);
17+
}
18+
19+
public static void validateAreaCodes(String[] areaCodes) {
20+
if (areaCodes != null) {
21+
for (String code : areaCodes) {
22+
if (!isValidAreaCode(code)) {
23+
throw new IllegalArgumentException("Invalid areaCode: " + code);
24+
}
25+
}
26+
}
27+
}
28+
29+
public static void validateMunicipalityCodes(String[] municipalityCodes) {
30+
if (municipalityCodes != null) {
31+
for (String code : municipalityCodes) {
32+
if (!isValidMunicipalityCode(code)) {
33+
throw new IllegalArgumentException("Invalid municipalityCode: " + code);
34+
}
35+
}
36+
}
37+
}
38+
}

src/ext/java/org/rutebanken/tiamat/ext/fintraffic/api/FintrafficApiController.java

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.rutebanken.tiamat.ext.fintraffic.api;
22

33
import jakarta.servlet.http.HttpServletResponse;
4+
import org.rutebanken.tiamat.ext.fintraffic.FintrafficConstants;
45
import org.rutebanken.tiamat.ext.fintraffic.api.model.FintrafficReadApiSearchKey;
56
import org.rutebanken.tiamat.model.VehicleModeEnumeration;
67
import org.springframework.context.annotation.Profile;
@@ -17,9 +18,6 @@
1718
@Controller
1819
public class FintrafficApiController {
1920
private final ReadApiNetexPublicationDeliveryService readApiNetexPublicationDeliveryService;
20-
private static final String AREA_CODE_REGEX = "[A-ZÅÄÖ]{3}";
21-
// 3 digits for normal municipalities, 4 digits for special areas like Haaparanta and Eurooppa
22-
private static final String MUNICIPALITY_CODE_REGEX = "\\d{3,4}";
2321

2422
public FintrafficApiController(
2523
ReadApiNetexPublicationDeliveryService readApiNetexPublicationDeliveryService
@@ -36,8 +34,8 @@ public void getNetexStream(
3634
) {
3735
try {
3836
validateTransportModes(transportMode);
39-
validateAreaCodes(areaCode);
40-
validateMunicipalityCodes(municipalityCode);
37+
FintrafficConstants.validateAreaCodes(areaCode);
38+
FintrafficConstants.validateMunicipalityCodes(municipalityCode);
4139
} catch (IllegalArgumentException e) {
4240
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
4341
return;
@@ -62,24 +60,4 @@ private void validateTransportModes(String[] transportModes) {
6260
}
6361
}
6462
}
65-
66-
private void validateAreaCodes(String[] areaCodes) {
67-
if (areaCodes != null) {
68-
for (String code : areaCodes) {
69-
if (!code.matches(AREA_CODE_REGEX)) {
70-
throw new IllegalArgumentException("Invalid areaCode: " + code);
71-
}
72-
}
73-
}
74-
}
75-
76-
private void validateMunicipalityCodes(String[] municipalityCodes) {
77-
if (municipalityCodes != null) {
78-
for (String code : municipalityCodes) {
79-
if (!code.matches(MUNICIPALITY_CODE_REGEX)) {
80-
throw new IllegalArgumentException("Invalid municipalityCode: " + code);
81-
}
82-
}
83-
}
84-
}
8563
}

0 commit comments

Comments
 (0)