Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ public enum ExceptionCodes implements ErrorHandler {
RESOURCE_RETRIEVAL_FAILED(900402, "Resource retrieval failed", 400, "Resource retrieval failed"),
USER_MAPPING_RETRIEVAL_FAILED(900404, "User mapping retrieval failed", 404, "User mapping retrieval failed"),
MALFORMED_URL(900403, "Malformed URL", 400, "Malformed URL"),
UNTRUSTED_URL(900405, "URL could not be resolved", 400,
"The provided URL could not be resolved."),
UNTRUSTED_URL_IN_DEFINITION(900407, "Remote reference could not be resolved", 400,
"A remote reference in the definition could not be resolved."),
NETWORK_SECURITY_ACCESS_CONTROL_MISCONFIGURED(900406,
"Internal server error. Please contact the system administrator.", 500,
"Internal server error. Please contact the system administrator."),

// Endpoint related codes
ENDPOINT_NOT_FOUND(900450, "Endpoint Not Found", 404, "Endpoint Not Found"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.List;

/**
* Model class to hold OpenAPI Specification parser options.
*/
Expand All @@ -28,10 +30,29 @@ public class OASParserOptions {

private boolean explicitStyleAndExplode = true;
private Integer yamlCodePointLimit = null;
private List<String> remoteRefAllowList;
private List<String> remoteRefBlockList;
private boolean networkAccessControlEnabled = false;

public OASParserOptions() {
}

/**
* Copy-constructor. Copies fields directly (no re-conversion) so that already-computed values such as
* {@code yamlCodePointLimit} are not re-interpreted through their String setters.
*
* @param other the instance to copy from; if {@code null}, this instance retains its default values
*/
public OASParserOptions(OASParserOptions other) {
if (other != null) {
this.explicitStyleAndExplode = other.explicitStyleAndExplode;
this.yamlCodePointLimit = other.yamlCodePointLimit;
this.remoteRefAllowList = other.remoteRefAllowList;
this.remoteRefBlockList = other.remoteRefBlockList;
this.networkAccessControlEnabled = other.networkAccessControlEnabled;
}
}

public boolean isExplicitStyleAndExplode() {
return explicitStyleAndExplode;
}
Expand Down Expand Up @@ -84,4 +105,36 @@ public void setYamlCodePointLimit(String snakeYamlMaxFileSizeLimit) {
this.yamlCodePointLimit = limit > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) limit;
}

}
public List<String> getRemoteRefAllowList() {
return remoteRefAllowList;
}

public void setRemoteRefAllowList(List<String> remoteRefAllowList) {
this.remoteRefAllowList = remoteRefAllowList;
}

public List<String> getRemoteRefBlockList() {
return remoteRefBlockList;
}

public void setRemoteRefBlockList(List<String> remoteRefBlockList) {
this.remoteRefBlockList = remoteRefBlockList;
}

public boolean isNetworkAccessControlEnabled() {
return networkAccessControlEnabled;
}

/**
* Configure whether the network access-control policy is in force for remote {@code $ref} resolution. When
* {@code false} (the default), the parser retains its historical behaviour and resolves remote refs without any
* host validation - preserving backwards compatibility for deployments that have not configured the policy. It
* is set to {@code true} only when a platform or tenant network access-control policy is present.
*
* @param networkAccessControlEnabled whether the network access-control policy is configured
*/
public void setNetworkAccessControlEnabled(boolean networkAccessControlEnabled) {
this.networkAccessControlEnabled = networkAccessControlEnabled;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.wso2.carbon.apimgt.api.model;

import org.junit.Assert;
import org.junit.Test;

import java.util.Arrays;

public class OASParserOptionsTest {
@Test
public void testRemoteRefListsDefaultNullAndRoundTrip() {
OASParserOptions o = new OASParserOptions();
Assert.assertNull(o.getRemoteRefAllowList());
Assert.assertNull(o.getRemoteRefBlockList());
// Backwards compatibility: network access control is off unless explicitly enabled.
Assert.assertFalse(o.isNetworkAccessControlEnabled());
o.setRemoteRefAllowList(Arrays.asList("*.wso2.com", "api.github.com"));
o.setRemoteRefBlockList(Arrays.asList("*.internal"));
o.setNetworkAccessControlEnabled(true);
Assert.assertEquals(2, o.getRemoteRefAllowList().size());
Assert.assertEquals("*.internal", o.getRemoteRefBlockList().get(0));
Assert.assertTrue(o.isNetworkAccessControlEnabled());
}

@Test
public void testCopyConstructorCopiesFieldsRaw() {
OASParserOptions that = new OASParserOptions();
that.setExplicitStyleAndExplode("false");
that.setYamlCodePointLimit("10");
that.setRemoteRefAllowList(Arrays.asList("*.wso2.com", "api.github.com"));
that.setRemoteRefBlockList(Arrays.asList("*.internal"));
that.setNetworkAccessControlEnabled(true);

OASParserOptions copy = new OASParserOptions(that);

Assert.assertEquals(that.isExplicitStyleAndExplode(), copy.isExplicitStyleAndExplode());
// Must be copied verbatim (already a code-point count), not re-converted as if it were MB.
Assert.assertEquals(that.getYamlCodePointLimit(), copy.getYamlCodePointLimit());
Assert.assertNotEquals(Integer.valueOf(Integer.MAX_VALUE), copy.getYamlCodePointLimit());
Assert.assertEquals(that.getRemoteRefAllowList(), copy.getRemoteRefAllowList());
Assert.assertEquals(that.getRemoteRefBlockList(), copy.getRemoteRefBlockList());
Assert.assertTrue(copy.isNetworkAccessControlEnabled());

OASParserOptions fromNull = new OASParserOptions(null);
Assert.assertNull(fromNull.getRemoteRefAllowList());
Assert.assertNull(fromNull.getRemoteRefBlockList());
Assert.assertNull(fromNull.getYamlCodePointLimit());
Assert.assertTrue(fromNull.isExplicitStyleAndExplode());
Assert.assertFalse(fromNull.isNetworkAccessControlEnabled());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4050,4 +4050,24 @@ public static class SynapseArtifactGenerator {
public static final String QUEUE_CAPACITY = THREAD_POOL_CONFIG + "QueueCapacity";
}

// Constants related to network security access control
public static class NetworkSecurityAccessControl {

private static final String CONFIG_PREFIX = "NetworkSecurityAccessControl.";
public static final String ENABLED = CONFIG_PREFIX + "Enabled";
public static final String MODE = CONFIG_PREFIX + "Mode";
public static final String HOSTS = CONFIG_PREFIX + "Host";
public static final String BLOCK_PRIVATE_NETWORK_ACCESS = CONFIG_PREFIX + "BlockPrivateNetworkAccess";
public static final String MODE_ALLOW = "allow";
public static final String MODE_DENY = "deny";
// Wildcard deny-list entry for the remote-$ref resolver: matches every host, so an allow-mode policy denies
// everything not on its allow-list (a restrictive whitelist).
public static final String MATCH_ALL_HOSTS = "*";

// Tenant config JSON keys (under "NetworkSecurityAccessControl" in tenant-conf.json)
public static final String TENANT_CONFIG_KEY = "NetworkSecurityAccessControl";
public static final String TENANT_MODE = "Mode";
public static final String TENANT_HOSTS = "Hosts";
public static final String TENANT_BLOCK_PRIVATE_NETWORK_ACCESS = "BlockPrivateNetworkAccess";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
import org.wso2.carbon.apimgt.spec.parser.definitions.OAS3Parser;
import org.wso2.carbon.apimgt.spec.parser.definitions.OASParserUtil;
import org.wso2.carbon.base.ServerConfiguration;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.core.util.CryptoException;
import org.wso2.carbon.core.util.CryptoUtil;
import org.wso2.carbon.utils.CarbonUtils;
Expand Down Expand Up @@ -659,19 +660,22 @@ public static APIDefinitionValidationResponse validateOpenAPIDefinition(String u
boolean returnContent)
throws APIManagementException {
APIDefinitionValidationResponse validationResponse = new APIDefinitionValidationResponse();
OASParserOptions parserOptions = ServiceReferenceHolder.getInstance().getAPIMDependencyConfigurationService()
.getAPIMDependencyConfigurations().getOasParserOptions();
OASParserOptions baseParserOptions = ServiceReferenceHolder.getInstance()
.getAPIMDependencyConfigurationService().getAPIMDependencyConfigurations().getOasParserOptions();
OASParserOptions parserOptions = APIUtil.buildRefResolutionOptions(baseParserOptions,
CarbonContext.getThreadLocalCarbonContext().getTenantDomain());
// Resolve the configured import size limit once so all validation paths honor it.
String maxContentSizeStr = ServiceReferenceHolder.getInstance().getAPIManagerConfigurationService()
.getAPIManagerConfiguration().getFirstProperty(
org.wso2.carbon.apimgt.api.APIConstants.API_PUBLISHER_IMPORT_OAS_FILE_SIZE_LIMIT);
if (maxContentSizeStr == null || maxContentSizeStr.trim().isEmpty()) {
maxContentSizeStr = org.wso2.carbon.apimgt.api.
APIConstants.API_PUBLISHER_IMPORT_OAS_FILE_SIZE_LIMIT_DEFAULT_MB;
}
if (url != null) {
try {
URL urlObj = new URL(url);
HttpClient httpClient = APIUtil.getHttpClient(urlObj.getPort(), urlObj.getProtocol());
String maxContentSizeStr = ServiceReferenceHolder.getInstance().getAPIManagerConfigurationService()
.getAPIManagerConfiguration().getFirstProperty(
org.wso2.carbon.apimgt.api.APIConstants.API_PUBLISHER_IMPORT_OAS_FILE_SIZE_LIMIT);
if (maxContentSizeStr == null || maxContentSizeStr.trim().isEmpty()) {
maxContentSizeStr = org.wso2.carbon.apimgt.api.
APIConstants.API_PUBLISHER_IMPORT_OAS_FILE_SIZE_LIMIT_DEFAULT_MB;
}
validationResponse = OASParserUtil.validateAPIDefinitionByURL(url, httpClient, returnContent,
parserOptions, maxContentSizeStr);
} catch (MalformedURLException e) {
Expand All @@ -682,7 +686,7 @@ public static APIDefinitionValidationResponse validateOpenAPIDefinition(String u
if (fileName != null) {
if (fileName.endsWith(".zip")) {
validationResponse = OASParserUtil.extractAndValidateOpenAPIArchive(inputStream, returnContent,
parserOptions);
parserOptions, maxContentSizeStr);
} else {
String openAPIContent = IOUtils.toString(inputStream, CHARSET);
validationResponse = OASParserUtil.validateAPIDefinition(openAPIContent, returnContent,
Expand Down
Loading
Loading