Skip to content

Commit

Permalink
Merge pull request #12073 from dulithsenanayake/context-validation-ma…
Browse files Browse the repository at this point in the history
…ster

Validate api context when creating APIs & API Products
  • Loading branch information
dulithsenanayake committed Jul 5, 2023
2 parents 823debb + 93dd8b5 commit 5af7105
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public enum ExceptionCodes implements ErrorHandler {

API_PRODUCT_CONTEXT_ALREADY_EXISTS(900275, "The API Product context already exists.", 409, "An API Product with context '%s' already exists"),

API_CONTEXT_MALFORMED_EXCEPTION(900253, "The API context is malformed.", 400, "'%s'"),
API_ALREADY_EXISTS(900300, "The API already exists.", 409, "The API already exists"),
APPLICATION_ALREADY_EXISTS(900301, "The application already exists.", 409, "The application already exists"),
APIMGT_DAO_EXCEPTION(900302, "Internal server error.", 500, "Error occurred while persisting/retrieving data"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public final class APIConstants {
//key value of the APIImpl rxt
public static final String API_KEY = "api";

public static final String VERSION_PLACEHOLDER = "{version}";

//governance registry apimgt root location
public static final String APIMGT_REGISTRY_LOCATION = "/apimgt";

Expand Down Expand Up @@ -1468,8 +1470,6 @@ private OAuthConstants() {
public static final String API_MANAGER_HOSTNAME = "HostName";
public static final String API_MANAGER_HOSTNAME_UNKNOWN = "UNKNOWN_HOST";

public static final String VERSION_PLACEHOLDER = "{version}";

public enum SupportedHTTPVerbs {
GET,
POST,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.security.cert.X509Certificate;
import javax.validation.constraints.NotNull;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
Expand All @@ -309,6 +310,8 @@ public final class APIUtil {

public static final String DISABLE_ROLE_VALIDATION_AT_SCOPE_CREATION = "disableRoleValidationAtScopeCreation";

public static final String DISABLE_API_CONTEXT_VALIDATION = "disableApiContextValidation";

private static final int ENTITY_EXPANSION_LIMIT = 0;

private static final String DESCRIPTION = "Allows [1] request(s) per minute.";
Expand Down Expand Up @@ -340,6 +343,7 @@ public final class APIUtil {

private static Schema tenantConfigJsonSchema;
private static Schema operationPolicySpecSchema;
private static final String contextRegex = "^[a-zA-Z0-9_${}/.;()-]+$";

private APIUtil() {

Expand Down Expand Up @@ -2751,6 +2755,96 @@ public static String replaceEmailDomainBack(String input) {
return input;
}

/**
* This method is used to get the APIProvider instance for a given username
*
* @param context API Context of the API
* @throws APIManagementException If the context is not valid
*/
public static void validateAPIContext(String context, String apiName) throws APIManagementException {
String disableAPIContextValidation = System.getProperty(DISABLE_API_CONTEXT_VALIDATION);
if (Boolean.parseBoolean(disableAPIContextValidation)) {
return;
}
Pattern pattern = Pattern.compile(contextRegex);
String errorMsg = ExceptionCodes.API_CONTEXT_MALFORMED_EXCEPTION.getErrorMessage();

if (context == null || context.isEmpty()) {
errorMsg = errorMsg + " For API " + apiName + ", context cannot be empty or null";
log.error(errorMsg);
throw new APIManagementException(errorMsg);
}

if (context.endsWith("/")) {
errorMsg = errorMsg + " For API " + apiName + ", context " + context + " cannot end with /";
log.error(errorMsg);
throw new APIManagementException(errorMsg);
}

Matcher matcher = pattern.matcher(context);

// if the context has allowed characters
if (matcher.matches()) {
context = context.startsWith("/") ? context : "/".concat(context);
String split[] = context.split("/");

for (String param : split) {
if (param != null && !APIConstants.VERSION_PLACEHOLDER.equals(param)) {
if (param.contains(APIConstants.VERSION_PLACEHOLDER)) {
errorMsg = errorMsg + " For API " + apiName +
", {version} cannot exist as a substring of a sub-context";
log.error(errorMsg);
throw new APIManagementException(errorMsg);
} else if (param.contains("{") || param.contains("}")) {
errorMsg = errorMsg + " For API " + apiName +
", { or } cannot exist as a substring of a sub-context";
log.error(errorMsg);
throw new APIManagementException(errorMsg);
}
}
}

//check whether the parentheses are balanced
boolean isBalanced = checkBalancedParentheses(context);
if (!isBalanced) {
errorMsg = errorMsg + " Unbalanced parenthesis cannot be used in context " + context + " for API "
+ apiName;
throw new APIManagementException(errorMsg);
}
} else {
errorMsg = errorMsg + " Special characters cannot be used in context " + context + " for API "+ apiName;
log.error(errorMsg);
throw new APIManagementException(errorMsg);
}
}

/**
* Check whether the parentheses are balanced
*
* @param input API Context
* @throws APIManagementException If parentheses are not balanced
*/
public static boolean checkBalancedParentheses(@NotNull String input) throws APIManagementException {
int count = 0;
for (int i = 0; i < input.length(); i++) {
if (input.charAt(i) == '(') {
count++;
} else if (input.charAt(i) == ')') {
count--;
}
if (count < 0) {
log.error("Unbalanced parentheses in : " + input);
return false;
}
}
if (count > 0) {
log.error("Unbalanced parentheses in : " + input);
return false;
}
return true;
}


public static void copyResourcePermissions(String username, String sourceArtifactPath, String targetArtifactPath)
throws APIManagementException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ public static API importApi(String extractedFolderPath, APIDTO importedApiDTO, B
// The status of the importing API should be stored separately to do the lifecycle change at the end
targetStatus = importedApiDTO.getLifeCycleStatus();

// validate the API context
APIUtil.validateAPIContext(importedApiDTO.getContext(), importedApiDTO.getName());

API targetApi = retrieveApiToOverwrite(importedApiDTO.getName(), importedApiDTO.getVersion(),
currentTenantDomain, apiProvider, Boolean.TRUE, organization);

Expand Down Expand Up @@ -412,6 +415,9 @@ public static API importApi(String extractedFolderPath, APIDTO importedApiDTO, B
errorMessage +=
importedApi.getId().getApiName() + StringUtils.SPACE + APIConstants.API_DATA_VERSION + ": "
+ importedApi.getId().getVersion();
} else if (e.getMessage().contains(ExceptionCodes.API_CONTEXT_MALFORMED_EXCEPTION.getErrorMessage())) {
throw new APIManagementException("Error while importing API: " + e.getMessage(),
ExceptionCodes.from(ExceptionCodes.API_CONTEXT_MALFORMED_EXCEPTION, e.getMessage()));
}
throw new APIManagementException(errorMessage + StringUtils.SPACE + e.getMessage(), e);
}
Expand Down Expand Up @@ -2301,6 +2307,9 @@ public static APIProduct importApiProduct(String extractedFolderPath, Boolean pr
}
}

// Validate API Product Context
APIUtil.validateAPIContext(importedApiProductDTO.getContext(), importedApiProductDTO.getName());

APIProvider apiProvider = RestApiCommonUtil.getLoggedInUserProvider();

// Check whether the API resources are valid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,14 @@ public static API addAPIWithGeneratedSwaggerDefinition(APIDTO apiDto, String oas
username = StringUtils.isEmpty(username) ? RestApiCommonUtil.getLoggedInUsername() : username;
APIProvider apiProvider = RestApiCommonUtil.getLoggedInUserProvider();

// validate context before proceeding
try {
APIUtil.validateAPIContext(apiDto.getContext(), apiDto.getName());
} catch (APIManagementException e) {
throw new APIManagementException("Error while importing API: " + e.getMessage(),
ExceptionCodes.from(ExceptionCodes.API_CONTEXT_MALFORMED_EXCEPTION, e.getMessage()));
}

// validate web socket api endpoint configurations
if (isWSAPI && !PublisherCommonUtils.isValidWSAPI(apiDto)) {
throw new APIManagementException("Endpoint URLs should be valid web socket URLs",
Expand Down Expand Up @@ -1710,6 +1718,10 @@ public static APIProduct addAPIProductWithGeneratedSwaggerDefinition(APIProductD
// if not add product
String provider = apiProductDTO.getProvider();
String context = apiProductDTO.getContext();

// Validate the API context
APIUtil.validateAPIContext(context, apiProductDTO.getName());

if (!StringUtils.isBlank(provider) && !provider.equals(username)) {
if (!APIUtil.hasPermission(username, APIConstants.Permissions.APIM_ADMIN)) {
if (log.isDebugEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,10 @@ public Response getAllAPIProducts(Integer limit, Integer offset, String query, S
return Response.created(createdApiProductUri).entity(createdApiProductDTO).build();

} catch (APIManagementException | FaultGatewaysException e) {
if (e.getMessage().contains(ExceptionCodes.API_CONTEXT_MALFORMED_EXCEPTION.getErrorMessage())) {
RestApiUtil.handleBadRequest("Error while adding new API Product. "
+ e.getMessage().replace("API", "API Product"), e, log);
}
String errorMessage = "Error while adding new API Product : " + provider + "-" + body.getName()
+ " - " + e.getMessage();
RestApiUtil.handleInternalServerError(errorMessage, e, log);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,12 @@ public Response importOpenAPIDefinition(InputStream fileInputStream, Attachment
APIDTO apiDTOFromProperties;
try {
apiDTOFromProperties = objectMapper.readValue(additionalProperties, APIDTO.class);
try {
APIUtil.validateAPIContext(apiDTOFromProperties.getContext(), apiDTOFromProperties.getName());
} catch (APIManagementException e) {
throw new APIManagementException(e.getMessage(),
ExceptionCodes.from(ExceptionCodes.API_CONTEXT_MALFORMED_EXCEPTION, e.getMessage()));
}
} catch (IOException e) {
throw RestApiUtil.buildBadRequestException("Error while parsing 'additionalProperties'", e);
}
Expand Down Expand Up @@ -2945,6 +2951,12 @@ public Response importWSDLDefinition(InputStream fileInputStream, Attachment fil

// Minimum requirement name, version, context and endpointConfig.
additionalPropertiesAPI = new ObjectMapper().readValue(additionalProperties, APIDTO.class);
try {
APIUtil.validateAPIContext(additionalPropertiesAPI.getContext(), additionalPropertiesAPI.getName());
} catch (APIManagementException e) {
throw new APIManagementException(e.getMessage(),
ExceptionCodes.from(ExceptionCodes.API_CONTEXT_MALFORMED_EXCEPTION, e.getMessage()));
}
String username = RestApiCommonUtil.getLoggedInUsername();
additionalPropertiesAPI.setProvider(username);
additionalPropertiesAPI.setType(APIDTO.TypeEnum.fromValue(implementationType));
Expand Down Expand Up @@ -3329,6 +3341,7 @@ public Response importGraphQLSchema(String ifMatch, String type, InputStream fil
}

additionalPropertiesAPI = new ObjectMapper().readValue(additionalProperties, APIDTO.class);
APIUtil.validateAPIContext(additionalPropertiesAPI.getContext(), additionalPropertiesAPI.getName());
additionalPropertiesAPI.setType(APIDTO.TypeEnum.GRAPHQL);
String organization = RestApiUtil.getValidatedOrganization(messageContext);
APIProvider apiProvider = RestApiCommonUtil.getLoggedInUserProvider();
Expand All @@ -3351,6 +3364,9 @@ public Response importGraphQLSchema(String ifMatch, String type, InputStream fil
URI createdApiUri = new URI(RestApiConstants.RESOURCE_PATH_APIS + "/" + createdApiDTO.getId());
return Response.created(createdApiUri).entity(createdApiDTO).build();
} catch (APIManagementException e) {
if (e.getMessage().contains(ExceptionCodes.API_CONTEXT_MALFORMED_EXCEPTION.getErrorMessage())) {
RestApiUtil.handleBadRequest(e.getMessage(), e, log);
}
String errorMessage = "Error while adding new API : " + additionalPropertiesAPI.getProvider() + "-" +
additionalPropertiesAPI.getName() + "-" + additionalPropertiesAPI.getVersion() + " - " + e.getMessage();
RestApiUtil.handleInternalServerError(errorMessage, e, log);
Expand Down Expand Up @@ -3913,6 +3929,12 @@ public Response importAsyncAPISpecification(InputStream fileInputStream, Attachm
if (apiDTOFromProperties.getType() == null) {
RestApiUtil.handleBadRequest("Required property protocol is not specified for the Async API", log);
}
try {
APIUtil.validateAPIContext(apiDTOFromProperties.getContext(), apiDTOFromProperties.getName());
} catch (APIManagementException e) {
throw new APIManagementException(e.getMessage(),
ExceptionCodes.from(ExceptionCodes.API_CONTEXT_MALFORMED_EXCEPTION, e.getMessage()));
}
} catch (IOException e) {
throw RestApiUtil.buildBadRequestException("Error while parsing 'additionalProperties'", e);
}
Expand Down Expand Up @@ -4055,6 +4077,7 @@ public Response importServiceFromCatalog(String serviceKey, APIDTO apiDto, Messa
if (service == null) {
RestApiUtil.handleResourceNotFoundError("Service", serviceKey, log);
}
APIUtil.validateAPIContext(apiDto.getContext(), apiDto.getName());
APIDTO createdApiDTO = null;
String organization = RestApiUtil.getValidatedOrganization(messageContext);
if (ServiceEntry.DefinitionType.OAS2.equals(service.getDefinitionType()) ||
Expand Down Expand Up @@ -4087,6 +4110,10 @@ public Response importServiceFromCatalog(String serviceKey, APIDTO apiDto, Messa
} catch (APIManagementException e) {
if (RestApiUtil.isDueToResourceNotFound(e)) {
RestApiUtil.handleResourceNotFoundError("Service", serviceKey, e, log);
} else if (e.getMessage().contains(ExceptionCodes.API_CONTEXT_MALFORMED_EXCEPTION.getErrorMessage())) {
RestApiUtil.handleBadRequest(e.getMessage(), e, log);
} else if (e.getMessage().contains("duplicate API context")) {
RestApiUtil.handleBadRequest(e.getMessage(), e, log);
} else {
String errorMessage = "Error while creating API using Service with Id : " + serviceKey
+ " from Service Catalog";
Expand Down

0 comments on commit 5af7105

Please sign in to comment.