Skip to content
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
525825c
wip
richard-salac Jun 10, 2026
ae41f74
Merge branch 'v2.x.x' into reboot/fix/permissive_cors
Jun 10, 2026
f18cb88
wip cors
Jun 12, 2026
40ec014
Merge remote-tracking branch 'origin/v2.x.x' into reboot/fix/permissi…
Jun 12, 2026
60b4832
wip
Jun 15, 2026
0643096
Merge remote-tracking branch 'origin/v2.x.x' into reboot/fix/permissi…
Jun 15, 2026
c572e4b
wip
Jun 15, 2026
ef67682
wip fix tests
Jun 15, 2026
face0e1
try fix timer (cherry pick from v3)
Jun 16, 2026
3587465
tests passing locally on macos
Jun 16, 2026
c67657a
wip build ok
Jun 16, 2026
d569af4
Merge remote-tracking branch 'origin/v2.x.x' into reboot/fix/permissi…
Jun 17, 2026
6f2a29f
Merge branch 'v2.x.x' into reboot/fix/permissive_cors
pablocarle Jun 17, 2026
19695ee
Merge branch 'reboot/fix/permissive_cors' of https://github.com/zowe/…
Jun 17, 2026
d0f2dad
Merge branch 'v2.x.x' into reboot/fix/permissive_cors
pablocarle Jun 18, 2026
4a90242
wip fix tests
Jun 18, 2026
4f8c82d
wip IT
Jun 18, 2026
103d2b5
fix for internal tests IT
Jun 19, 2026
35cb56d
fix for sonar issues
Jun 19, 2026
c624cd3
Merge branch 'v2.x.x' into reboot/fix/permissive_cors
pablocarle Jun 19, 2026
7a31507
wip acceptance test
Jun 19, 2026
71cea27
Merge branch 'reboot/fix/permissive_cors' of https://github.com/zowe/…
Jun 19, 2026
2af0566
passing
Jun 19, 2026
bdbcc7b
scgw
Jun 22, 2026
13e6f27
Merge branch 'v2.x.x' into reboot/fix/permissive_cors
pablocarle Jun 22, 2026
e841a92
remove cors cloud gateway
Jun 22, 2026
0789575
Merge branch 'reboot/fix/permissive_cors' of https://github.com/zowe/…
Jun 22, 2026
728e69b
update start.sh
Jun 22, 2026
a99149a
add default headers logic
Jun 23, 2026
88cdb74
wip tests
Jun 24, 2026
e300c28
Merge remote-tracking branch 'origin/v2.x.x' into reboot/fix/permissi…
Jun 24, 2026
0b1ed91
update eclipse settings
Jun 25, 2026
e995e27
Merge remote-tracking branch 'origin/v2.x.x' into reboot/fix/permissi…
Jun 26, 2026
9cbe777
Merge remote-tracking branch 'origin/v2.x.x' into reboot/fix/permissi…
Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ jobs:
APIML_SECURITY_AUTH_PASSTICKET_CUSTOMUSERHEADER: customUserHeader
APIML_SECURITY_AUTH_PASSTICKET_CUSTOMAUTHHEADER: customPassticketHeader
ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_DISCOVERYSERVICEURLS: https://discovery-service-2:10011/eureka
APIML_SERVICE_CORSENABLED: true
APIML_SERVICE_CORSDEFAULTALLOWEDORIGINS: https://localhost:10010,https://localhost2:10010
mock-services:
image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }}
metrics-service:
Expand Down Expand Up @@ -679,6 +681,7 @@ jobs:
APIML_HEALTH_PROTECTED: true
APIML_SECURITY_AUTH_PASSTICKET_CUSTOMUSERHEADER: customUserHeader
APIML_SECURITY_AUTH_PASSTICKET_CUSTOMAUTHHEADER: customPassticketHeader
APIML_SERVICE_CORSDEFAULTALLOWEDORIGINS: https://localhost:10010,https://localhost2:10010
mock-services:
image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }}
metrics-service:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.context.annotation.Profile;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -37,7 +36,6 @@
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
@ActiveProfiles("ResponseHeaderFixTest")
@DirtiesContext
class ResponseHeaderFixTest {

private static final int ADD_HEADER = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.zowe.apiml.apicatalog.ApiCatalogApplication;
Expand All @@ -47,7 +46,6 @@
class AttlsConfigTest {

@Nested
@DirtiesContext
@ActiveProfiles({"AttlsConfigTestCatalog", "attlsServer", "attlsClient"})
class GivenAttlsModeEnabled extends ApiCatalogFunctionalTest {

Expand Down Expand Up @@ -103,7 +101,6 @@ void requestFailsWithAttlsContextReasonWithHttp() {
}
)
@ActiveProfiles({"attlsServer", "attlsClient", "debug"})
@DirtiesContext
@SpringBootTest(
classes = ApiCatalogApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
Expand Down
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ allprojects {

eclipse {
classpath {
defaultOutputDir = file('build/classes/java/main')
file {
whenMerged { classpath ->
for (ClasspathEntry entry : classpath.getEntries()) {
Expand All @@ -68,6 +69,12 @@ allprojects {
if (theEntry.path != null && theEntry.path.contains('org.eclipse.jdt.launching.JRE_CONTAINER')) {
theEntry.accessRules.add(new AccessRule('accessible', 'com/sun/net/httpserver/**'))
}
if (theEntry.path != null && (theEntry.path == 'src/main/java' || theEntry.path == 'src/main/resources')) {
theEntry.output = 'build/classes/java/main'
}
if (theEntry.path != null && (theEntry.path == 'src/test/java' || theEntry.path == 'src/test/resources')) {
theEntry.output = 'build/classes/java/test'
}
}
}
}
Expand Down Expand Up @@ -146,6 +153,8 @@ subprojects {

tasks.withType(Test) {
maxParallelForks = 1
maxHeapSize = '2g'
systemProperty 'spring.test.context.cache.maxSize', '5'
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.context.annotation.Profile;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -43,7 +42,6 @@
)
@ActiveProfiles("ResponseHeaderFixTest")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DirtiesContext
class ResponseHeaderFixTest {

private static final int ADD_HEADER = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.zowe.apiml.caching.CachingService;
Expand Down Expand Up @@ -60,7 +59,6 @@ private String getUri(String hostname, int port, String scheme) {
"caching.storage.mode=inMemory"
}
)
@DirtiesContext
@Nested
class GivenAttlsModeEnabled {

Expand Down Expand Up @@ -131,7 +129,6 @@ void requestFailsWithAttlsReasonWithHttp() {
}
)
@ActiveProfiles({"attlsServer", "attlsClient"})
@DirtiesContext
@SpringBootTest(
classes = CachingService.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
Expand Down
2 changes: 0 additions & 2 deletions cloud-gateway-package/src/main/resources/bin/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,6 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${CLOUD_GATEWAY_CODE} java \
-Dapiml.service.hostname=${ZWE_haInstance_hostname:-localhost} \
-Dapiml.service.port=${ZWE_configs_port:-10023} \
-Dapiml.service.forwardClientCertEnabled=${ZWE_configs_apiml_service_forwardClientCertEnabled:-false} \
-Dapiml.service.corsEnabled=${ZWE_configs_apiml_service_corsEnabled:-false} \
-Dapiml.service.corsAllowedMethods=${ZWE_configs_apiml_service_corsAllowedMethods:-} \
-Dapiml.security.x509.registry.allowedUsers=${ZWE_configs_apiml_security_x509_registry_allowedUsers:-} \
-Dapiml.logs.location=${ZWE_zowe_logDirectory} \
-Dapiml.zoweManifest=${ZWE_zowe_runtimeDirectory}/manifest.json \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public class ConnectionsConfig {
private int requestTimeout;

@Value("${apiml.service.corsEnabled:false}")
private boolean corsEnabled;
private boolean gatewayCorsEnabled;

@Value("${apiml.service.corsAllowedMethods:GET,HEAD,POST,PATCH,DELETE,PUT,OPTIONS}")
private List<String> corsAllowedMethods;
Expand Down Expand Up @@ -356,7 +356,10 @@ public CorsConfigurationSource corsConfigurationSource(RoutePredicateHandlerMapp

@Bean
public CorsUtils corsUtils() {
return new CorsUtils(corsEnabled, corsAllowedMethods, null);
return CorsUtils.builder()
.gatewayCorsEnabled(gatewayCorsEnabled)
.defaultAllowedCorsHttpMethods(corsAllowedMethods)
.build();
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,32 @@

package org.zowe.apiml.cloudgatewayservice.service;

import lombok.AccessLevel;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.zowe.apiml.auth.Authentication;
import org.zowe.apiml.auth.AuthenticationScheme;
import org.zowe.apiml.cloudgatewayservice.service.routing.RouteDefinitionProducer;
import org.zowe.apiml.cloudgatewayservice.service.scheme.SchemeHandler;
import org.zowe.apiml.eurekaservice.client.util.EurekaMetadataParser;
import org.zowe.apiml.product.routing.RoutedService;
import org.zowe.apiml.util.CorsUtils;
import org.zowe.apiml.util.StringUtils;
import reactor.core.publisher.Flux;

import java.util.*;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.zowe.apiml.constants.EurekaMetadataDefinition.APIML_ID;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.SERVICE_SUPPORTING_CLIENT_CERT_FORWARDING;

@Service
Expand All @@ -47,28 +46,18 @@ public class RouteLocator implements RouteDefinitionLocator {
@Value("${apiml.service.forwardClientCertEnabled:false}")
private boolean forwardingClientCertEnabled;

private final ApplicationContext context;

private final CorsUtils corsUtils;
private final ReactiveDiscoveryClient discoveryClient;

private final List<FilterDefinition> commonFilters;
private final List<RouteDefinitionProducer> routeDefinitionProducers;
private final Map<AuthenticationScheme, SchemeHandler> schemeHandlers = new EnumMap<>(AuthenticationScheme.class);

@Getter(lazy = true, value = AccessLevel.PRIVATE)
private final UrlBasedCorsConfigurationSource corsConfigurationSource = context.getBean(UrlBasedCorsConfigurationSource.class);

public RouteLocator(
ApplicationContext context,
CorsUtils corsUtils,
ReactiveDiscoveryClient discoveryClient,
List<FilterDefinition> commonFilters,
List<SchemeHandler> schemeHandlersList,
List<RouteDefinitionProducer> routeDefinitionProducers
) {
this.context = context;
this.corsUtils = corsUtils;
this.discoveryClient = discoveryClient;
this.commonFilters = commonFilters;
this.routeDefinitionProducers = routeDefinitionProducers;
Expand All @@ -93,16 +82,6 @@ void setAuth(ServiceInstance serviceInstance, RouteDefinition routeDefinition, A
}
}

void setCors(ServiceInstance serviceInstance) {
corsUtils.setCorsConfiguration(
serviceInstance.getServiceId().toLowerCase(),
serviceInstance.getMetadata(),
(prefix, serviceId, config) -> {
serviceId = serviceInstance.getMetadata().getOrDefault(APIML_ID, serviceInstance.getServiceId().toLowerCase());
getCorsConfigurationSource().registerCorsConfiguration("/" + serviceId + "/**", config);
});
}

Stream<RoutedService> getRoutedService(ServiceInstance serviceInstance) {
// TODO: this is till the SCGW and GW uses the same DS. The routing rules should be different for each application
if (org.apache.commons.lang.StringUtils.equalsIgnoreCase("GATEWAY", serviceInstance.getServiceId())) {
Expand Down Expand Up @@ -175,13 +154,9 @@ public Flux<RouteDefinition> getRouteDefinitions() {
// counter of generated route definition to prevent clashing by the order
AtomicInteger order = new AtomicInteger();
// iterate over services
return getServiceInstances().flatMap(Flux::fromIterable).map(serviceInstance -> {
// configure CORS for the service (if necessary)
setCors(serviceInstance);

return getServiceInstances().flatMap(Flux::fromIterable).map(serviceInstance ->
// generate route definition per services and its routing rules
return getAuthFilterPerRoute(order, serviceInstance, getPostRoutingFilters(serviceInstance));
})
getAuthFilterPerRoute(order, serviceInstance, getPostRoutingFilters(serviceInstance)))
.flatMapIterable(list -> list);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.zowe.apiml.cloudgatewayservice.acceptance;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.zowe.apiml.cloudgatewayservice.acceptance.common.AcceptanceTest;
import org.zowe.apiml.cloudgatewayservice.acceptance.common.AcceptanceTestWithMockServices;
Expand All @@ -27,6 +28,7 @@ class CorsPerServiceTest extends AcceptanceTestWithMockServices {
private static final String HEADER_X_FORWARD_TO = "X-Forward-To";

@Test
@Disabled("CORS Not supported in SCGW. Using Spring's defaults")
void routeToServiceWithCorsEnabled() throws IOException {
mockService("serviceid1")
.addEndpoint("/test")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@
@Retention(RetentionPolicy.RUNTIME)
@ComponentScan(basePackages = "org.zowe.apiml.cloudgatewayservice")
@SpringBootTest(classes = CloudGatewayServiceTestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"management.server.port=10091","server.internal.enabled=false"})
properties = {
"management.server.port=-1",
"server.internal.enabled=false"
}
)
@Import(DiscoveryClientTestConfig.class)
@DirtiesContext
public @interface AcceptanceTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.springframework.test.util.ReflectionTestUtils;
import org.zowe.apiml.util.CorsUtils;

import java.lang.reflect.Field;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -131,12 +130,10 @@ public class WhenCorsAllowedMethodsIsNotSet {
private ConnectionsConfig connectionsConfig;

@Test
void validateDefaultCorsAllowedMethods() throws NoSuchFieldException, IllegalAccessException {
void validateDefaultCorsAllowedMethods() {
CorsUtils corsUtils = connectionsConfig.corsUtils();

Field field = corsUtils.getClass().getDeclaredField("allowedCorsHttpMethods");
field.setAccessible(true);
List<String> corsAllowedMethods = (List<String>) field.get(corsUtils);
@SuppressWarnings("unchecked")
List<String> corsAllowedMethods = (List<String>) ReflectionTestUtils.getField(corsUtils, "defaultAllowedCorsHttpMethods");
assertEquals(7, corsAllowedMethods.size());
}
}
Expand All @@ -152,18 +149,19 @@ public class WhenCorsAllowedMethodsIsSet {
private ConnectionsConfig connectionsConfig;

@Test
void validateCorsAllowedMethods() throws NoSuchFieldException, IllegalAccessException {
void validateCorsAllowedMethods() {
CorsUtils corsUtils = connectionsConfig.corsUtils();

Field field = corsUtils.getClass().getDeclaredField("allowedCorsHttpMethods");
field.setAccessible(true);
List<String> corsAllowedMethods = (List<String>) field.get(corsUtils);
@SuppressWarnings("unchecked")
List<String> corsAllowedMethods = (List<String>) ReflectionTestUtils.getField(corsUtils, "defaultAllowedCorsHttpMethods");
assertEquals(3, corsAllowedMethods.size());
assertEquals("GET", corsAllowedMethods.get(0));
assertEquals("POST", corsAllowedMethods.get(1));
assertEquals("PATCH", corsAllowedMethods.get(2));
}

}

}

}
Expand Down
Loading
Loading