Skip to content

Commit

Permalink
Merge pull request eclipse-kapua#4117 from Agnul97/feature-propagate-…
Browse files Browse the repository at this point in the history
…exceptions-jaxb

⚡ [REST API] Block execution on XML/JSON parsing errors and report 400 error code
  • Loading branch information
Coduz authored Oct 28, 2024
2 parents ca43672 + be39606 commit d5e338d
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 3 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/kapua-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,18 @@ jobs:
tag: '@rest_cors'
needs-docker-images: 'true'
needs-api-docker-image: 'true'
test-api-parsing:
needs: test-api-auth
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Clones Kapua repo inside the runner
uses: actions/checkout@v4
- uses: ./.github/actions/runTestsTaggedAs
with:
tag: '@rest_parsing'
needs-docker-images: 'true'
needs-api-docker-image: 'true'
junit-tests:
needs: build
runs-on: ubuntu-latest
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2021, 2022 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Eurotech - initial API and implementation
*******************************************************************************/
package org.eclipse.kapua.commons.rest.errors;

import org.eclipse.kapua.commons.rest.model.errors.ThrowableInfo;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.EclipseLinkException;
import org.eclipse.persistence.exceptions.XMLConversionException;

import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class EclipseLinkExceptionMapper implements ExceptionMapper<EclipseLinkException> {

private static final Logger LOG = LoggerFactory.getLogger(EclipseLinkExceptionMapper.class);

private final boolean showStackTrace;

@Inject
public EclipseLinkExceptionMapper(ExceptionConfigurationProvider exceptionConfigurationProvider) {
this.showStackTrace = exceptionConfigurationProvider.showStackTrace();
}

@Override
public Response toResponse(EclipseLinkException eclipseException) {
LOG.error(eclipseException.getMessage(), eclipseException);

if (eclipseException instanceof ConversionException ||
eclipseException instanceof DescriptorException ||
eclipseException instanceof XMLMarshalException ||
eclipseException instanceof XMLConversionException) { // These are subset of EclipseLinkExceptions thrown by MOXy, so we have a problem with JAXB parsing of the XML/JSON
ThrowableInfo responseError = new ThrowableInfo(Response.Status.BAD_REQUEST.getStatusCode(), eclipseException, showStackTrace);
responseError.setMessage("An error occurred during the parsing of the XML/JSON. Check the correctness of its format. Details of the exception thrown: " + responseError.getMessage());
return Response
.status(Response.Status.BAD_REQUEST)
.entity(responseError)
.build();
} else {
//Generic error message for other EclipseLinkExceptions
return Response
.serverError()
.entity(new ThrowableInfo(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), eclipseException, showStackTrace))
.build();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ public void restCallInternal(String method, String resource, String json, boolea
baseBuilder.POST(HttpRequest.BodyPublishers.ofString(json));
} else if (method.equals("GET")) {
baseBuilder.GET();
} else if (method.equals("PUT")) {
baseBuilder.setHeader("Content-Type", "application/json");
baseBuilder.PUT(HttpRequest.BodyPublishers.ofString(json));
}

if (additionalHeaders != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2018, 2022 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Eurotech
*******************************************************************************/
package org.eclipse.kapua.integration.rest;

import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
@CucumberOptions(
features = "classpath:features/rest/parsingRequests/RestParsingRequestsContents.feature",
glue = {"org.eclipse.kapua.qa.common",
"org.eclipse.kapua.qa.integration.steps",
"org.eclipse.kapua.service.endpoint.steps",
"org.eclipse.kapua.service.account.steps",
"org.eclipse.kapua.service.user.steps"
},
plugin = { "pretty",
"html:target/cucumber/RestCors",
"json:target/RestCors_cucumber.json"
},
monochrome = true)
public class RunRestParsingRequestTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
###############################################################################
# Copyright (c) 2018, 2022 Eurotech and/or its affiliates and others
#
# This program and the accompanying materials are made
# available under the terms of the Eclipse Public License 2.0
# which is available at https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# Eurotech - initial API and implementation
###############################################################################
@env_docker_base
@rest_parsing

Feature: REST API tests for parsing of requests
REST API tests to verify correct responses upon parsing of wrong format fields in requests

@setup
Scenario: Initialize security context, then start rest-api container and dependencies
Given Init Security Context
And start rest-API container and dependencies with auth token TTL "10000"ms and refresh token TTL "20000"ms and cors endpoint refresh interval 5s

Scenario: Creation of a device with a wrong format "status" field
api back-end parser (MOXy) should spot error on the format and the mico-service should reply with 400 error code

Given Server with host "127.0.0.1" on port "8081"
Given An authenticated user
When REST "POST" call at "/v1/_/devices" with JSON "{\"clientId\": \"wrongStatusDevice\", \"status\": \"FOOOO\"}"
Then REST response code is 400
And REST response containing text "An error occurred during the parsing of the XML/JSON"

Scenario: Update of "credentialService" configuration missing to include the "type" field but providing the "value" field
api back-end parser (MOXy) should spot error on the format and the mico-service should reply with 400 error code
Given Server with host "127.0.0.1" on port "8081"
Given An authenticated user
When REST "PUT" call at "/v1/_/serviceConfigurations/org.eclipse.kapua.service.authentication.credential.CredentialService" with JSON "{\"id\": \"org.eclipse.kapua.service.authentication.credential.CredentialService\", \"properties\": {\"property\": [{\"name\": \"password.minLength\", \"array\": false, \"encrypted\": false, \"value\": [\"13\"]}]}}"
Then REST response code is 400
And REST response containing text "An error occurred during the parsing of the XML/JSON"
And REST response containing text "Illegal 'null' value for 'property.type' for parameter: password.minLength"

Scenario: Update of an user setting a wrong format "expirationDate" field
api back-end parser (MOXy) should spot error on the format and the micro-service should reply with 400 error code
Given Server with host "127.0.0.1" on port "8081"
Given An authenticated user
#now let's try to update kapua-broker with a not parsable expiration date
When REST "PUT" call at "/v1/_/users/Ag" with JSON "{\"optlock\": 1, \"name\": \"kapua-broker\", \"expirationDate\": \"randomwrongvaluenotparsabledate\"}"
Then REST response code is 400
And REST response containing text "An error occurred during the parsing of the XML/JSON"

Scenario: Update of "credentialService" configuration missing to include the "type" field AND the "value" field
api back-end parser (MOXy) should NOT spot error because we are trying to update a property with a "default" value
Given Server with host "127.0.0.1" on port "8081"
Given An authenticated user
#the json is a huge string but basically it's a complete set of properties for the CredentialService, where the "password.minLength" property we want to set has missing "type" and "value" fields (we want to set an unlimited value for it)
When REST "PUT" call at "/v1/_/serviceConfigurations/org.eclipse.kapua.service.authentication.credential.CredentialService" with JSON "{\"id\": \"org.eclipse.kapua.service.authentication.credential.CredentialService\", \"properties\": {\"property\": [{\"name\": \"lockoutPolicy.resetAfter\", \"array\": false, \"encrypted\": false, \"type\": \"Integer\", \"value\": [\"3800\"]}, {\"name\": \"password.minLength\", \"array\": false, \"encrypted\": false}, {\"name\": \"lockoutPolicy.lockDuration\", \"array\": false, \"encrypted\": false, \"type\": \"Integer\", \"value\": [\"10800\"]}, {\"name\": \"lockoutPolicy.enabled\", \"array\": false, \"encrypted\": false, \"type\": \"Boolean\", \"value\": [\"true\"]}, {\"name\": \"lockoutPolicy.maxFailures\", \"array\": false, \"encrypted\": false, \"type\": \"Integer\", \"value\": [\"3\"]}]}}"
Then REST response code is 204

@teardown
Scenario: Stop full docker environment
Given Stop full docker environment
And Clean Locator Instance


Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*******************************************************************************
* Copyright (c) 2016, 2022 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Eurotech - initial API and implementation
*******************************************************************************/
package org.eclipse.kapua.app.api.web;

import org.glassfish.jersey.internal.InternalProperties;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.helpers.DefaultValidationEventHandler;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

public class MoxyJsonFeatureCustomJsonProvider implements Feature {
//A custom feature used to set a custom moxyJsonProvider

private static final String JSON_FEATURE = MoxyJsonFeatureCustomJsonProvider.class.getSimpleName();

@Override
public boolean configure(final FeatureContext context) {
final Configuration config = context.getConfiguration();
// Disable other JSON providers. In this way the org.glassfish.jersey.moxy.json.MoxyJsonFeature (registered as default by MOXy) will skip the registration of the default provider
context.property(PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE, config.getRuntimeType()),
JSON_FEATURE);
context.register(CustomMoxyJsonProvider.class);
return true;
}

@Provider
public static class CustomMoxyJsonProvider extends org.glassfish.jersey.moxy.json.internal.ConfigurableMoxyJsonProvider {
//A custom moxyJsonProvider that sets the unmarshaller validationEventHandler to the default one. This one allows to propagate exceptions to the stack when an error is found (for example, when an exception has been thrown from one of our custom "xmlAdapters")
@Override
protected void preReadFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, Unmarshaller unmarshaller) throws JAXBException {
super.preReadFrom(type, genericType, annotations, mediaType, httpHeaders, unmarshaller);
unmarshaller.setEventHandler(new DefaultValidationEventHandler());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ protected void configure() {
property(ServerProperties.WADL_FEATURE_DISABLE, true);

//Manually adding MOXyJSONFeature
register(org.glassfish.jersey.moxy.json.MoxyJsonFeature.class);
register(MoxyJsonConfigContextResolver.class);
register(UriConnegFilter.class);
register(JaxbContextResolver.class);
register(KapuaSerializableBodyWriter.class);
register(ListBodyWriter.class);
register(MoxyJsonFeatureCustomJsonProvider.class);

register(new ContainerLifecycleListener() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public Map<String, Object> unmarshal(V[] properties) {
.stream()
.peek(adaptedProp -> {
if (adaptedProp.getType() == null && adaptedProp.getValues() != null) {
throw new InternalError("null value for property.type parameter");
throw new IllegalArgumentException("Illegal 'null' value for 'property.type' for parameter: " + adaptedProp.getName());
}
})
.filter(adaptedProp -> xmlPropertyAdapters.containsKey((adaptedProp.getType())))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ public void testUnmarshallingMissingType() {
//and an instance
final XmlPropertiesAdapter instance = new TestPropertiesAdapter(adapters);
//When I unmarshal
Assert.assertThrows(InternalError.class, () -> instance.unmarshal(new TestPropertyAdapted[]{
Assert.assertThrows(IllegalArgumentException.class, () -> instance.unmarshal(new TestPropertyAdapted[]{
new TestPropertyAdapted("aString", TestTypes.First, "TheString"),
new TestPropertyAdapted("aBoolean", TestTypes.Second, "false", "true"),
new TestPropertyAdapted("anotherValue", null, "42")
Expand Down

0 comments on commit d5e338d

Please sign in to comment.