diff --git a/src/ext-test/java/org/rutebanken/tiamat/ext/fintraffic/api/FintrafficSearchKeyServiceTest.java b/src/ext-test/java/org/rutebanken/tiamat/ext/fintraffic/api/FintrafficSearchKeyServiceTest.java new file mode 100644 index 000000000..e1f265bd0 --- /dev/null +++ b/src/ext-test/java/org/rutebanken/tiamat/ext/fintraffic/api/FintrafficSearchKeyServiceTest.java @@ -0,0 +1,160 @@ +package org.rutebanken.tiamat.ext.fintraffic.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.rutebanken.tiamat.ext.fintraffic.api.model.FintrafficReadApiSearchKey; +import org.rutebanken.tiamat.model.StopPlace; +import org.rutebanken.tiamat.model.VehicleModeEnumeration; +import org.rutebanken.tiamat.repository.StopPlaceRepository; +import org.rutebanken.tiamat.repository.TopographicPlaceRepository; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class FintrafficSearchKeyServiceTest { + + private FintrafficSearchKeyService service; + + @BeforeEach + void setUp() { + TopographicPlaceRepository topographicPlaceRepositoryMock = mock(TopographicPlaceRepository.class); + StopPlaceRepository stopPlaceRepositoryMock = mock(StopPlaceRepository.class); + when(topographicPlaceRepositoryMock.findTopographicPlace(any())).thenReturn(List.of()); + + service = new FintrafficSearchKeyService( + new ObjectMapper(), + topographicPlaceRepositoryMock, + stopPlaceRepositoryMock + ); + } + + private StopPlace createStopPlace(String netexId, VehicleModeEnumeration transportMode) { + return createStopPlace(netexId, transportMode, 1L); + } + + private StopPlace createStopPlace(String netexId, VehicleModeEnumeration transportMode, long version) { + StopPlace stopPlace = new StopPlace(); + stopPlace.setNetexId(netexId); + stopPlace.setTransportMode(transportMode); + stopPlace.setVersion(version); + return stopPlace; + } + + @Test + void generateSearchKeyJSON_regularStopPlace_containsTransportMode() { + StopPlace stopPlace = createStopPlace("FSR:StopPlace:1", VehicleModeEnumeration.BUS); + + String json = service.generateSearchKeyJSON(stopPlace); + + assertThat(json).contains("bus"); + } + + @Test + void generateSearchKeyJSON_regularStopPlace_noTransportMode_emptyArray() { + StopPlace stopPlace = createStopPlace("FSR:StopPlace:2", null); + + String json = service.generateSearchKeyJSON(stopPlace); + + // transportModes should be empty + assertThat(json).contains("\"transportModes\":[]"); + } + + @Test + void generateSearchKeyJSON_parentStopPlace_collectsChildTransportModes() { + StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:10", null); + parentStopPlace.setParentStopPlace(true); + + StopPlace child1 = createStopPlace("FSR:StopPlace:11", VehicleModeEnumeration.BUS, 1L); + StopPlace child2 = createStopPlace("FSR:StopPlace:12", VehicleModeEnumeration.TRAM, 2L); + HashSet children = new HashSet<>(); + children.add(child1); + children.add(child2); + parentStopPlace.setChildren(children); + + String json = service.generateSearchKeyJSON(parentStopPlace); + + assertThat(json).contains("bus"); + assertThat(json).contains("tram"); + } + + @Test + void generateSearchKeyJSON_parentStopPlace_deduplicatesChildTransportModes() { + StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:20", null); + parentStopPlace.setParentStopPlace(true); + + StopPlace child1 = createStopPlace("FSR:StopPlace:21", VehicleModeEnumeration.BUS, 1L); + StopPlace child2 = createStopPlace("FSR:StopPlace:22", VehicleModeEnumeration.BUS, 2L); + HashSet children = new HashSet<>(); + children.add(child1); + children.add(child2); + parentStopPlace.setChildren(children); + + String json = service.generateSearchKeyJSON(parentStopPlace); + + FintrafficReadApiSearchKey searchKey = parseSearchKey(json); + long busCount = Arrays.stream(searchKey.transportModes()).filter("bus"::equals).count(); + assertThat(busCount).isEqualTo(1); + } + + @Test + void generateSearchKeyJSON_parentStopPlace_mergesParentAndChildTransportModes() { + StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:30", VehicleModeEnumeration.WATER, 1L); + parentStopPlace.setParentStopPlace(true); + + StopPlace child = createStopPlace("FSR:StopPlace:31", VehicleModeEnumeration.BUS, 2L); + HashSet children = new HashSet<>(); + children.add(child); + parentStopPlace.setChildren(children); + + String json = service.generateSearchKeyJSON(parentStopPlace); + + assertThat(json).contains("water"); + assertThat(json).contains("bus"); + } + + @Test + void generateSearchKeyJSON_parentStopPlace_childWithNullTransportMode_ignored() { + StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:40", null); + parentStopPlace.setParentStopPlace(true); + + StopPlace childWithMode = createStopPlace("FSR:StopPlace:41", VehicleModeEnumeration.BUS, 1L); + StopPlace childNoMode = createStopPlace("FSR:StopPlace:42", null, 2L); + HashSet children = new HashSet<>(); + children.add(childWithMode); + children.add(childNoMode); + parentStopPlace.setChildren(children); + + String json = service.generateSearchKeyJSON(parentStopPlace); + + assertThat(json).contains("bus"); + FintrafficReadApiSearchKey searchKey = parseSearchKey(json); + assertThat(searchKey.transportModes()).containsExactly("bus"); + } + + @Test + void generateSearchKeyJSON_parentStopPlace_noChildren_emptyTransportModes() { + StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:50", null); + parentStopPlace.setParentStopPlace(true); + parentStopPlace.setChildren(Set.of()); + + String json = service.generateSearchKeyJSON(parentStopPlace); + + assertThat(json).contains("\"transportModes\":[]"); + } + + private FintrafficReadApiSearchKey parseSearchKey(String json) { + try { + return new ObjectMapper().readValue(json, FintrafficReadApiSearchKey.class); + } catch (Exception e) { + throw new RuntimeException("Failed to parse search key JSON: " + json, e); + } + } +} diff --git a/src/ext-test/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficAuthorizationServiceTest.java b/src/ext-test/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficAuthorizationServiceTest.java index 2d5b0178f..5cf16ff4f 100644 --- a/src/ext-test/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficAuthorizationServiceTest.java +++ b/src/ext-test/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficAuthorizationServiceTest.java @@ -8,6 +8,7 @@ import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; import org.rutebanken.tiamat.exporter.params.TopographicPlaceSearch; +import org.rutebanken.tiamat.model.GroupOfStopPlaces; import org.rutebanken.tiamat.model.Parking; import org.rutebanken.tiamat.model.Quay; import org.rutebanken.tiamat.model.StopPlace; @@ -24,10 +25,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Mockito.mock; @@ -93,10 +92,14 @@ class FintrafficAuthorizationServiceTest { } private static FintrafficAuthorizationService getAuthorizationService() { - return getAuthorizationService(true, true); + return getAuthorizationService(true, true, false); } - private static FintrafficAuthorizationService getAuthorizationService(boolean codespaceEnabled, boolean municipalityEnabled) { + private static FintrafficAuthorizationService getAuthorizationService( + boolean codespaceEnabled, + boolean municipalityEnabled, + boolean multiModalStopPlaceSupportDisabled + ) { TopographicPlaceRepository topographicPlaceRepositoryMock = mock(TopographicPlaceRepository.class); TrivoreAuthorizations trivoreAuthorizationsMock = mock(TrivoreAuthorizations.class); @@ -117,12 +120,14 @@ private static FintrafficAuthorizationService getAuthorizationService(boolean co when(trivoreAuthorizationsMock.hasAccess(matches("Parking"), matches("\\{all\\}"), eq(TrivorePermission.MANAGE), anyBoolean())).thenReturn(true); when(trivoreAuthorizationsMock.hasAccess(matches("Quay"), matches("\\{all\\}"), eq(TrivorePermission.MANAGE), anyBoolean())).thenReturn(true); when(trivoreAuthorizationsMock.hasAccess(matches("StopPlace"), matches("RAIL"), eq(TrivorePermission.MANAGE), anyBoolean())).thenReturn(false); + when(trivoreAuthorizationsMock.hasAccess(matches("GroupOfStopPlaces"), matches("\\{all\\}"), eq(TrivorePermission.MANAGE), anyBoolean())).thenReturn(true); return new FintrafficAuthorizationService( trivoreAuthorizationsMock, topographicPlaceRepositoryMock, codespaceEnabled, - municipalityEnabled + municipalityEnabled, + multiModalStopPlaceSupportDisabled ); } @@ -160,6 +165,69 @@ public void testCanEditEntityParking() { } + @Test + public void testCanEditParentStopPlaceAllChildTransportModesAllowed() { + // Parent stop place with children that all have allowed transport modes (BUS) + StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:100", null, getPoint(new Coordinate(0.3, 0.3))); + parentStopPlace.setParentStopPlace(true); + StopPlace child1 = getStopPlace("FSR:StopPlace:101", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3))); + StopPlace child2 = getStopPlace("FSR:StopPlace:102", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.4, 0.4))); + parentStopPlace.setChildren(Set.of(child1, child2)); + + FintrafficAuthorizationService authorizationService = getAuthorizationService(); + assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(true)); + } + + @Test + public void testCanEditParentStopPlaceChildTransportModeNotAllowed() { + // Parent stop place with a child that has a forbidden transport mode (RAIL) + StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:200", null, getPoint(new Coordinate(0.3, 0.3))); + parentStopPlace.setParentStopPlace(true); + StopPlace child = getStopPlace("FSR:StopPlace:201", VehicleModeEnumeration.RAIL, getPoint(new Coordinate(0.3, 0.3))); + parentStopPlace.setChildren(Set.of(child)); + + FintrafficAuthorizationService authorizationService = getAuthorizationService(); + assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false)); + } + + @Test + public void testCanNotEditParentStopPlaceMultiModalSupportDisabled() { + // Parent stop place with children that all have allowed transport modes (BUS) + // Multi-modal stop place support is disabled, so parent stop place should not be editable even if child transport mode is allowed + StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:100", null, getPoint(new Coordinate(0.3, 0.3))); + parentStopPlace.setParentStopPlace(true); + StopPlace child1 = getStopPlace("FSR:StopPlace:101", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3))); + StopPlace child2 = getStopPlace("FSR:StopPlace:102", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.4, 0.4))); + parentStopPlace.setChildren(Set.of(child1, child2)); + + FintrafficAuthorizationService authorizationService = getAuthorizationService(true, true, true); + assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false)); + } + + @Test + public void testCanEditParentStopPlaceWithMixedChildTransportModes() { + // Parent stop place with children of mixed transport modes: one allowed (BUS), one denied (RAIL) + StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:300", null, getPoint(new Coordinate(0.3, 0.3))); + parentStopPlace.setParentStopPlace(true); + StopPlace childBus = getStopPlace("FSR:StopPlace:301", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3))); + StopPlace childRail = getStopPlace("FSR:StopPlace:302", VehicleModeEnumeration.RAIL, getPoint(new Coordinate(0.4, 0.4))); + parentStopPlace.setChildren(Set.of(childBus, childRail)); + + FintrafficAuthorizationService authorizationService = getAuthorizationService(); + assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false)); + } + + @Test + public void testCanEditParentStopPlaceWithNoChildren() { + // Parent stop place with no children + StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:400", null, getPoint(new Coordinate(0.3, 0.3))); + parentStopPlace.setParentStopPlace(true); + parentStopPlace.setChildren(Set.of()); + + FintrafficAuthorizationService authorizationService = getAuthorizationService(); + assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false)); + } + @Test public void testCanEditStopPlaceWithNestedEntities() { StopPlace stopPlaceWithQuayAndNestedStopPlace = getStopPlace("FSR:StopPlace:1", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3))); @@ -212,7 +280,7 @@ public void testCanEditEntityByEitherCodespaceOrMunicipality() { @Test public void testCodespaceOnlyMode() { - FintrafficAuthorizationService authorizationService = getAuthorizationService(true, false); + FintrafficAuthorizationService authorizationService = getAuthorizationService(true, false, false); // Point inside region (codespace) — allowed assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(0.5, 0.5))), equalTo(true)); // Point inside municipality area but municipality auth disabled — denied @@ -221,7 +289,7 @@ public void testCodespaceOnlyMode() { @Test public void testMunicipalityOnlyMode() { - FintrafficAuthorizationService authorizationService = getAuthorizationService(false, true); + FintrafficAuthorizationService authorizationService = getAuthorizationService(false, true, false); // Point inside region but codespace auth disabled — denied assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(0.5, 0.5))), equalTo(false)); // Point inside municipality area — allowed @@ -230,9 +298,18 @@ public void testMunicipalityOnlyMode() { @Test public void testBothAuthorizationMethodsDisabled() { - FintrafficAuthorizationService authorizationService = getAuthorizationService(false, false); + FintrafficAuthorizationService authorizationService = getAuthorizationService(false, false, false); // Both disabled — all geographic edits denied assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(0.5, 0.5))), equalTo(false)); assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(2.5, 2.5))), equalTo(false)); } + + @Test + public void testGroupOfStopPlacesEditable() { + GroupOfStopPlaces groupOfStopPlaces = new GroupOfStopPlaces(); + groupOfStopPlaces.setNetexId("FSR:GroupOfStopPlaces:1"); + + FintrafficAuthorizationService authorizationService = getAuthorizationService(); + assertThat(authorizationService.canEditEntity(groupOfStopPlaces), equalTo(true)); + } } diff --git a/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/api/FintrafficSearchKeyService.java b/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/api/FintrafficSearchKeyService.java index e21b8e4ac..ad3fbb415 100644 --- a/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/api/FintrafficSearchKeyService.java +++ b/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/api/FintrafficSearchKeyService.java @@ -20,6 +20,7 @@ import org.rutebanken.tiamat.model.StopPlace; import org.rutebanken.tiamat.model.TopographicPlace; import org.rutebanken.tiamat.model.TopographicPlaceTypeEnumeration; +import org.rutebanken.tiamat.model.VehicleModeEnumeration; import org.rutebanken.tiamat.repository.StopPlaceRepository; import org.rutebanken.tiamat.repository.TopographicPlaceRepository; import org.slf4j.Logger; @@ -152,6 +153,19 @@ private FintrafficReadApiSearchKey extractSearchKey(org.rutebanken.tiamat.model. String[] transportModes = stopPlace.getTransportMode() != null ? new String[]{stopPlace.getTransportMode().value()} : new String[]{}; + + // If StopPlace is parentStopPlace then get transportModes from child stopPlaces as well + if (stopPlace.isParentStopPlace() && stopPlace.getChildren() != null) { + String[] childTransportModes = stopPlace.getChildren().stream().map(org.rutebanken.tiamat.model.StopPlace::getTransportMode) + .filter(Objects::nonNull) + .map(VehicleModeEnumeration::value) + .toArray(String[]::new); + + transportModes = Stream.concat(Stream.of(transportModes), Stream.of(childTransportModes)) + .distinct() + .toArray(String[]::new); + } + Optional stopPlaceCentroid = Optional.ofNullable(stopPlace.getCentroid()); Optional areaCodes = stopPlaceCentroid.map(this::getAdministrativeZonesForPoint); diff --git a/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficAuthorizationService.java b/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficAuthorizationService.java index 8d23ec9d6..1ba5c6f66 100644 --- a/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficAuthorizationService.java +++ b/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficAuthorizationService.java @@ -9,6 +9,7 @@ import org.rutebanken.tiamat.exporter.params.ExportParams; import org.rutebanken.tiamat.exporter.params.TopographicPlaceSearch; import org.rutebanken.tiamat.model.EntityStructure; +import org.rutebanken.tiamat.model.FareZone; import org.rutebanken.tiamat.model.StopPlace; import org.rutebanken.tiamat.model.StopTypeEnumeration; import org.rutebanken.tiamat.model.TopographicPlace; @@ -47,6 +48,8 @@ public class FintrafficAuthorizationService implements AuthorizationService { private final boolean municipalityAuthorizationEnabled; + private final boolean multiModalStopPlaceSupportDisabled; + private final LoadingCache> fintrafficAdministrativeZoneCache; private final LoadingCache> municipalityCache; @@ -54,11 +57,13 @@ public class FintrafficAuthorizationService implements AuthorizationService { public FintrafficAuthorizationService(TrivoreAuthorizations trivoreAuthorizations, TopographicPlaceRepository topographicPlaceRepository, boolean codespaceAuthorizationEnabled, - boolean municipalityAuthorizationEnabled) { + boolean municipalityAuthorizationEnabled, + boolean multiModalStopPlaceSupportDisabled) { this.trivoreAuthorizations = trivoreAuthorizations; this.topographicPlaceRepository = topographicPlaceRepository; this.codespaceAuthorizationEnabled = codespaceAuthorizationEnabled; this.municipalityAuthorizationEnabled = municipalityAuthorizationEnabled; + this.multiModalStopPlaceSupportDisabled = multiModalStopPlaceSupportDisabled; logger.info("FintrafficAuthorizationService initialized: codespace authorization={}, municipality authorization={}", codespaceAuthorizationEnabled, municipalityAuthorizationEnabled); this.fintrafficAdministrativeZoneCache = CacheBuilder.newBuilder() @@ -122,10 +127,32 @@ public boolean canEditEntity(EntityStructure entity) { } private boolean canEditEntity(EntityStructure entity, boolean logAuthorizationCheck) { - if (entity == null) { - return true; + switch (entity) { + case null -> { + return true; + } + case TopographicPlace tp -> { + return trivoreAuthorizations.hasAccess(ENTITY_TYPE_ALL, TRANSPORT_MODE_ALL, ADMINISTER); + } + case FareZone fz -> { + return trivoreAuthorizations.hasAccess(ENTITY_TYPE_ALL, TRANSPORT_MODE_ALL, ADMINISTER); + } + case StopPlace stop when stop.isParentStopPlace() && multiModalStopPlaceSupportDisabled -> { + logger.warn("Access denied to entity {} because it is a parent StopPlace and multi-modal StopPlace support is disabled.", entity); + return false; + } + default -> { + } } - if (!trivoreAuthorizations.hasAccess(detectEntityType(entity), detectTransportMode(entity), MANAGE, logAuthorizationCheck)) { + + boolean isParentStopPlaceWithChildStops = entity instanceof StopPlace stop && stop.isParentStopPlace() && stop.getChildren() != null && !stop.getChildren().isEmpty(); + + if (entity instanceof StopPlace stop && isParentStopPlaceWithChildStops) { + if (!canEditChildStops(stop, logAuthorizationCheck)) { + return false; + } + } else if (!trivoreAuthorizations.hasAccess(detectEntityType(entity), detectTransportMode(entity), MANAGE, logAuthorizationCheck)) { + // For other entities, check the transport mode and entity type directly return false; } @@ -145,6 +172,19 @@ private boolean canEditEntity(EntityStructure entity, boolean logAuthorizationCh return true; } + /** + * Check if the user has permission to edit the child stops of a parent stop place. + * This is used to determine if the user can edit a parent stop place when it contains child stops that the user does not have access to. + * @param stopPlace the parent stop place to check the child stops of + * @param logAuthorizationCheck + * @return true if the user has permission to edit all child stops, otherwise false + */ + private boolean canEditChildStops(StopPlace stopPlace, boolean logAuthorizationCheck) { + // For parent stop places, require manage access to all transport modes and entity types of the children + // This is because parent stop places do not have transport modes set + return stopPlace.getChildren().stream().allMatch(e -> trivoreAuthorizations.hasAccess(detectEntityType(stopPlace), detectTransportMode(e), MANAGE, logAuthorizationCheck)); + } + private static String detectEntityType(EntityStructure entity) { return entity.getClass().getSimpleName(); } diff --git a/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficSecurityConfig.java b/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficSecurityConfig.java index 02a0d1db5..3de3938bb 100644 --- a/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficSecurityConfig.java +++ b/src/ext/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficSecurityConfig.java @@ -37,10 +37,17 @@ AuthorizationService authorizationService( @Value("${tiamat.ext.fintraffic.security.client-secret}") String clientSecret, @Value("${tiamat.ext.fintraffic.auth.codespace-authorization.enabled:true}") boolean codespaceAuthEnabled, @Value("${tiamat.ext.fintraffic.auth.municipality-authorization.enabled:false}") boolean municipalityAuthEnabled, + @Value("${tiamat.ext.fintraffic.auth.multiModal.disabled:false}") boolean multiModalStopPlaceSupportDisabled, TopographicPlaceRepository topographicPlaceRepository ) { TrivoreAuthorizations trivoreAuthorizations = new TrivoreAuthorizations(prepareWebClient(webClientBuilder), oidcServerUri, clientId, clientSecret); - return new FintrafficAuthorizationService(trivoreAuthorizations, topographicPlaceRepository, codespaceAuthEnabled, municipalityAuthEnabled); + return new FintrafficAuthorizationService( + trivoreAuthorizations, + topographicPlaceRepository, + codespaceAuthEnabled, + municipalityAuthEnabled, + multiModalStopPlaceSupportDisabled + ); } private WebClient prepareWebClient(WebClient.Builder webClientBuilder) {