How to prevent the duplication of keys in request body to improve spring application security? [duplicate] - spring

I'm using Jackson JSON library to convert some JSON objects to POJO classes. The problem is, when I use JSON Objects with duplicated properties like:
{
"name":"xiaopang",
"email":"xiaopang1#123.com",
"email":"xiaopang2#123.com"
}
Jackson report the last email pair "email":"xiaopang2#123.com" and then parse the object.
I've learned from Does JSON syntax allow duplicate keys in an object? that what happens when deserializing a JSON object with duplicate properties depends on the library implementation, either throwing an error or using the last one for duplicate key.
Despite overheads of tracking all properties, is there any way to tell Jackson to report an error or exception such as "Duplicate key" in this case?

Use JsonParser.Feature.STRICT_DUPLICATE_DETECTION
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
MyPOJO result = mapper.readValue(json, MyPOJO.class);
Results in:
Exception in thread "main" com.fasterxml.jackson.core.JsonParseException: Duplicate field 'email'
You can also try to use DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY (more info) It will be trigggered if you deserialize your json string/input to jackson json tree first and then to you POJO. Can combine it with custom JsonDeserializer like this:
private static class MyPojoDeserializer extends JsonDeserializer<MyPOJO>{
#Override
public MyPOJO deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException{
JsonNode tree = p.readValueAsTree();
return p.getCodec().treeToValue(tree, MyPOJO.class);
}
}
Setup it once and use it same way as before:
// setup ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
SimpleModule module = new SimpleModule();
module.addDeserializer(MyPOJO.class,new MyPojoDeserializer() );
mapper.registerModule(module);
// use
mapper.readValue(json, MyPOJO.class);
Result:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Duplicate field 'email' for ObjectNode: not allowed when FAIL_ON_READING_DUP_TREE_KEY enabled
Other options would be to implement all the logic yourself in custom deserializer or in you POJO setter methods.

Related

How to link a Vaadin Grid with the result of Spring Mono WebClient data

This seems to be a missing part in the documentation of Vaadin...
I call an API to get data in my UI like this:
#Override
public URI getUri(String url, PageRequest page) {
return UriComponentsBuilder.fromUriString(url)
.queryParam("page", page.getPageNumber())
.queryParam("size", page.getPageSize())
.queryParam("sort", (page.getSort().isSorted() ? page.getSort() : ""))
.build()
.toUri();
}
#Override
public Mono<Page<SomeDto>> getDataByPage(PageRequest pageRequest) {
return webClient.get()
.uri(getUri(URL_API + "/page", pageRequest))
.retrieve()
.bodyToMono(new ParameterizedTypeReference<>() {
});
}
In the Vaadin documentation (https://vaadin.com/docs/v10/flow/binding-data/tutorial-flow-data-provider), I found an example with DataProvider.fromCallbacks but this expects streams and that doesn't feel like the correct approach as I need to block on the requests to get the streams...
DataProvider<SomeDto, Void> lazyProvider = DataProvider.fromCallbacks(
q -> service.getData(PageRequest.of(q.getOffset(), q.getLimit())).block().stream(),
q -> service.getDataCount().block().intValue()
);
When trying this implementation, I get the following error:
org.springframework.core.codec.CodecException: Type definition error: [simple type, class org.springframework.data.domain.Page]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Page` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1]
grid.setItems(lazyProvider);
I don't have experience with vaadin, so i'll talk about the deserialization problem.
Jackson needs a Creator when deserializing. That's either:
the default no-arg constructor
another constructor annotated with #JsonCreator
static factory method annotated with #JsonCreator
If we take a look at spring's implementations of Page - PageImpl and GeoPage, they have neither of those. So you have two options:
Write your custom deserializer and register it with the ObjectMapper instance
The deserializer:
public class PageDeserializer<T> extends StdDeserializer<Page<T>> {
public PageDeserializer() {
super(Page.class);
}
#Override
public Page<T> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
//TODO implement for your case
return null;
}
}
And registration:
SimpleModule module = new SimpleModule();
module.addDeserializer(Page.class, new PageDeserializer<>());
objectMapper.registerModule(module);
Make your own classes extending PageImpl, PageRequest, etc. and annotate their constructors with #JsonCreator and arguments with #JsonProperty.
Your page:
public class MyPage<T> extends PageImpl<T> {
#JsonCreator
public MyPage(#JsonProperty("content_prop_from_json") List<T> content, #JsonProperty("pageable_obj_from_json") MyPageable pageable, #JsonProperty("total_from_json") long total) {
super(content, pageable, total);
}
}
Your pageable:
public class MyPageable extends PageRequest {
#JsonCreator
public MyPageable(#JsonProperty("page_from_json") int page, #JsonProperty("size_from_json") int size, #JsonProperty("sort_object_from_json") Sort sort) {
super(page, size, sort);
}
}
Depending on your needs for Sort object, you might need to create MySort as well, or you can remove it from constructor and supply unsorted sort, for example, to the super constructor. If you are deserializing from input manually you need to provide type parameters like this:
JavaType javaType = TypeFactory.defaultInstance().constructParametricType(MyPage.class, MyModel.class);
Page<MyModel> deserialized = objectMapper.readValue(pageString, javaType);
If the input is from request body, for example, just declaring the generic type in the variable is enough for object mapper to pick it up.
#PostMapping("/deserialize")
public ResponseEntity<String> deserialize(#RequestBody MyPage<MyModel> page) {
return ResponseEntity.ok("OK");
}
Personally i would go for the second option, even though you have to create more classes, it spares the tediousness of extracting properties and creating instances manually when writing deserializers.
There are two parts to this question.
The first one is about asynchronously loading data for a DataProvider in Vaadin. This isn't supported since Vaadin has prioritized the typical case with fetching data straight through JDBC. This means that you end up blocking a thread while the data is loading. Vaadin 23 will add support for doing that blocking on a separate thread instead of keeping the UI thread blocked, but it will still be blocking.
The other half of your problem doesn't seem to be directly related to Vaadin. The exception message says that the Jackson instance used by the REST client isn't configured to support creating instances of org.springframework.data.domain.Page. I don't have direct experience with this part of the problem, so I cannot give any advice on exactly how to fix it.

Ignoring Jackson InvalidFormatException to use #ExceptionHandler for #Valid #RequestBody

Basically, I'm trying to validate inputs from the #RequestBody of a REST controller using an #ExceptionHandler to catch MethodArgumentNotValidException constraints.
#PostMapping(value = "/createbaseline")
public ResponseEntity<?> createBaseline(#Valid #RequestPart Baseline baseline){
//...
return ResponseEntity.ok("...");
}
Before I can do that Jackson is throwing an InvalidFormatException when it fails to parse a string to a Date, thus preventing my #ExceptionHandler from validating the remaining inputs. Here is my #ExceptionHandler method that I want to use for validating inputs.
#ExceptionHandler(MethodArgumentNotValidException.class)
protected final ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex){
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String[] mapping = error.getDefaultMessage().split(":");
errors.put(mapping[0], mapping[1]);
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
I've considered just changing the Date property of the object to a String, but I know that's against best practices. The reason why I want to use the handleMethodArgumentNotValid is because I have custom constraint annotations on fields in my objects that specify a key that I can access via MethodArgumentNotValidException.getBindingResult() like so:
#RequiredFieldConstraint(key = "label")
private String label;
The client can then use those keys to determine which input field will show an error. If I used the method below to catch the InvalidFormatException then I don't have access to those keys specified by the field annotations.
#ExceptionHandler(value = {InvalidFormatException.class})
protected ResponseEntity<Object> handleException(Exception e){
//can't access annotation keys in this method body
return ResponseEntity.badRequest().body("bad");
}
I need to be able to validate ALL inputs sent in the #ResponseBody in order to send the appropriate error message back to the client but I'd still like to utilize deserializing the #RequestBody directly into an object. Does anyone have ideas on how to approach or work around this?
I found a work around for this by implementing a custom deserializer for Dates so that it catches the InvalidFormatException but still returns a new Date() with .setTime(-1) that I can check for in my handleMethodArgumentNotValid method along with all the other inputs.

How can I throw mapping to object exception with gson?

I'm trying to force gson to throw an exception when an string does not map to an object which I'm passing to it.
#ResponseStatus(HttpStatus.CREATED)
#PostMapping("offer")
public String postOffer(#RequestBody String jsonBody) {
Offer offer = gson.fromJson(jsonBody, Offer.class);
offerRepository.save(offer);
return offer.getId();
}
Currently, it will just save what ever it can to the db and ignore any elements that don't map to the class. This is bad for me because I get bad data making it's way to the db.
Any help would be appreciated.
ps. using springboot-data-mongodb and gson for mapping.
Thanks
In GSON you cannot make some fields required.
You can handle this i your code, if the variable is not present in json then in Offer object that variable will simple be assigned as null.
You can add null check to your code for the required fields and throw your own exception.
Since gson dont have this facility, you can also try the answer from below link-
Gson optional and required fields
To achieve this you need to follow two steps:-
1) Mark all required field in Offer class as #NotNull(message="your custom message")
2) Add below class to tell Mongo to validate document before persisting it to the database.
#Configuration
public class MongoEventValidationListener {
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}

RestTemplate getForObject to POJO

I'm using RestTemplate.getForObject() to retrieve json that includes a couple of objects and arrays, I only want to convert one of the objects inside this json to a POJO, I don't care about the other objects inside this json.
What is the proper way to approach this?
Edit:
Another approach from accepted answer, we can use jacksons ObjectMapper
#Autowired
private ObjectMapper jacksonObjectMapper;
then
LinkedHashMap obj1 = restTemplate.getForObject(uri, LinkedHashMap.class, params);
LinkedHashMap obj2 = (LinkedHashMap)test.get("flightStatuses");
Flight flight = jacksonObjectMapper.convertValue(obj2, Flight.class);
You get the idea, just get a generic datatype from your json structure then use ObjectMapper to convert it to the class you need.
One solution would be to create a wrapper class, which includes the POJO you want to deserialize and ignore all other properties using #JsonIgnoreProperties. You would then retrieve the wrapper object and get the POJO from it.
#JsonIgnoreProperties(ignoreUnknown=true)
public class Wrapper {
private MyPojo myPojo;
}
MyPojo myPojo = restTemplate.getForObject("url", Wrapper.class).getMyPojo();

GSON - Ignore parsing exception

Is there any way to force Gson to ignore any sort of parsing exception (just skip that field ) ?
Depending on the field you are looking for, you COULD just not serialize that object. For example:
public class test{
String somString;
Map<String,String> thisValueThrowsTheError;
Int somInt;
}
If you wanted to just IGNORE the Map object, you could do that like this:
public class test{
String somString;
#SerializeName("NOTAVALIDJSONOBJECTNAME")
Map<String,String> thisValueThrowsTheError;
Int somInt;
}
Gson won't see a name for that Map and it will skip that object.
Alternately (and ultimately the better solution) is just use a Deserializer to fix the issue that you have correctly. See this post as an example of a deserializer:
Gson deserialization - Trying to parse a JSON to an Object

Resources