I am getting alert in Checkmarx scan saying Unsafe object binding in the saveAll() call.
The exact words in checkmarx are -
The columnConfigSet at src\main\java\com\ge\digital\oa\moa\controller\ConfigController.java in line 45 may unintentionally allow setting the value of saveAll in setColumnsConfig, in the object src\main\java\com\ge\digital\oa\moa\service\ConfigService.java at line 170.
Any idea how to rewrite the code , so that the checkmarx stops complaining.
My code:
#PutMapping("/columns")
#ResponseStatus(OK)
public void setColumnsConfig(#RequestBody(required=true) ColumnConfigSetDto columnConfigSet) {
service.setColumnsConfig(columnConfigSet);
}
public void setColumnsConfig(ColumnConfigSetDto columnConfigSet) {
String userId = columnConfigSet.getUserId();
String viewName = columnConfigSet.getViewName();
List<ColumnConfig> configs = new ArrayList<>();
for (ColumnConfigDto colConfig : columnConfigSet.getColumns()) {
// build a db config row only for the visibility property for now
ColumnConfigId confId = new ColumnConfigId();
confId.setUserId(userId);
confId.setViewName(viewName);
confId.setKey(colConfig.getKey());
confId.setProperty("visible");
ColumnConfig conf = new ColumnConfig();
conf.setColumnConfigId(confId);
conf.setValue(colConfig.getIsVisible() ? "true" : "false" );
configs.add(conf);
}
if (!configs.isEmpty()) {
configRepo.saveAll(configs);
}
}
Below are my DTO Objects which is used in this code :
#Getter
#Setter
public class ColumnConfigSetDto {
#JsonProperty("userId")
private String userId;
#JsonProperty("viewName")
private String viewName;
#JsonProperty("columns")
private List<ColumnConfigDto> columns;
}
Below are my DTO code which is used in this
#Getter
#Setter
public class ColumnConfigDto {
#JsonProperty("key")
private String key;
#JsonProperty("label")
private String label;
#JsonProperty("isVisible")
private Boolean isVisible;
#JsonProperty("position")
private Integer position;
#JsonProperty("isSortable")
private Boolean isSortable;
#JsonProperty("isHideable")
private Boolean isHideable;
}
Here is my solution for Unsafe object binding reported by cherkmarx in Java.
It's not a graceful approach and only fix this vulnerability.
Remove all setter methods for boxed fields in each requestbody bean.
Since #JsonProperty could support deserialization capbility, no need to add setter manually.
If you need setter for request body bean indeed, you can use reflaction way instead.
FieldUtils.writeField(columnConfigDto , "isVisible", true, true);
public class ColumnConfigDto {
// Ensure #JsonProperty existed on each field
#JsonProperty("key")
private String key;
#JsonProperty("isVisible")
private Boolean isVisible;
#JsonProperty("list")
private List list;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Boolean getVisible() {
return isVisible;
}
// Remove boxed type field
// public void setVisible(Boolean visible) {
// isVisible = visible;
// }
public List getList() {
return list;
}
// Remove boxed type field
// public void setList(List list) {
// this.list = list;
// }
}
this issue occurs due to #RequestBoby as per spring documentation but there is no issue for #RequestParam. if we bind request body to object without #RequestBody, this issue is not occurred.
HttpServletRequest request;
mapper.readValue(request.getInputStream(), Product.class);
The error is also thrown if data is set to an object annotated with #RequestBody.
requestBodyVariable.setAdditionalValue(valueFromRequestParamOrPathVariable);
// This setter call should not be used
Instead, use a user-defined variable for storing the value from request param, header or path variable in its place:
service.callServiceMethod(requestBodyVariable, valueFromRequestParamOrPathVariable);
Related
I have following java class in a Springboot application
public enum Status {
DISABLED(false),
ENABLED(true);
private final boolean enabled;
Status(boolean value){
this.enabled = value;
}
public boolean value() {
return this.enabled;
}
/*
#JsonValue public boolean jsonValue() { return enabled; }
Error: Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `com.q.demo.model.Status` from Boolean value (token `JsonToken.VALUE_TRUE`);
*/
/*
public static Status forValue(#JsonProperty("enabled") Boolean status) {
if (status == null) {
return null;
}
if (Status.ENABLED.value() == status.booleanValue()) {
return Status.ENABLED;
} else {
return Status.DISABLED;
}
}
Error: Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Input mismatch reading Enum `com.q.demo.model.Status`: properties-based `#JsonCreator` ([method com.q.demo.model.Status#forValue(java.lang.Boolean)]) expects JSON Object (JsonToken.START_OBJECT), got JsonToken.VALUE_TRUE;
*/
}
public class User {
private Long userId;
private String userName;
private String role;
private String password;
private Status enabled;
//Getters and setters
}
I would like to serialize/deserialize json given below to the enum
{
"userName" : "usrer",
"role" : "role",
"password" : "psw",
"enabled" : true
}
I am not successful by using either #JsonProperty(which accepts String only) or #JsonValue (given in the code as commented line with error message) or #Jsconcreator (again code and error message given in the commented section. Can somebody give me a pointer? Jackson version is 2.13.0. Thank you.
All you need at this point is a way to tell Jackson to convert from and to your enum. You can combine #JsonValue and #JsonCreator inside your Status enum:
#JsonValue
public boolean value() {
return this.enabled;
}
#JsonCreator
public static Status of(boolean b) {
return b ? ENABLED : DISABLED;
}
#JsonValue (on the instance method) tells Jackson what value to use when serializing. The #JsonCreator annotation on that static method tells Jackson that the boolean taken from JSON can be used to resolve the corresponding enum value.
I am trying to see if I can replace my existing Pojos with the new Record classes in Java 14. But unable to do so. Getting following error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of com.a.a.Post (no Creators, like default
construct, exist): cannot deserialize from Object value (no delegate-
or property-based Creator)
I get that the error is saying the record has no constructors, but from what I see the record class takes care of it in the background and relevant getters are also set in the background (not getters exactly but id() title() and so on without the get prefix). Is it cos Spring has not adopted the latest Java 14 record yet? Please advice. Thanks.
I am doing this in Spring Boot version 2.2.6 and using Java 14.
The following works using the usual POJOs.
PostClass
public class PostClass {
private int userId;
private int id;
private String title;
private String body;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
Method to call rest service which works now as I am using the above POJO.
public PostClass[] getPosts() throws URISyntaxException {
String url = "https://jsonplaceholder.typicode.com/posts";
return template.getForEntity(new URI(url), PostClass[].class).getBody();
}
But if I switch to following where I am using record instead, I am getting the above error.
The new record class.
public record Post(int userId, int id, String title, String body) {
}
Changing the method to use the record instead which fails.
public Post[] getPosts() throws URISyntaxException {
String url = "https://jsonplaceholder.typicode.com/posts";
return template.getForEntity(new URI(url), Post[].class).getBody();
}
EDIT:
Tried adding constructors as follows to the record Post and same error:
public record Post(int userId, int id, String title, String body) {
public Post {
}
}
or
public record Post(int userId, int id, String title, String body) {
public Post(int userId, int id, String title, String body) {
this.userId = userId;
this.id = id;
this.title = title;
this.body = body;
}
}
It is possible with some Jackson Annotations, which cause Jackson to use fields instead of getters. Still far less verbose than a pre-Java 14 class (without Lombok or similar solutions).
record Foo(#JsonProperty("a") int a, #JsonProperty("b") int b){
}
This probably works because according to https://openjdk.java.net/jeps/359:
Declaration annotations are permitted on record components if they are
applicable to record components, parameters, fields, or methods.
Declaration annotations that are applicable to any of these targets
are propagated to implicit declarations of any mandated members.
See also: When is the #JsonProperty property used and what is it used for?
It is also possible to make use #JsonAutoDetect
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
record Bar(int a, int b){
}
If configuring the Objectmapper to use field Visibility globally, this annotation on class level is not needed.
See also: How to specify jackson to only use fields - preferably globally
Example:
public class Test {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(new Foo(1, 2))); //{"a":1,"b":2}
System.out.println(om.writeValueAsString(new Bar(3, 4))); //{"a":3,"b":4}
}
record Foo(#JsonProperty("a") int a, #JsonProperty("b") int b){
}
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
record Bar(int a, int b){
}
}
There is also a Github issue for that feature: https://github.com/FasterXML/jackson-future-ideas/issues/46
This is slated for jackson 2.12
https://github.com/FasterXML/jackson-future-ideas/issues/46
The compiler generates the constructor and other accessor method for a Record.
In your case,
public final class Post extends java.lang.Record {
public Post(int, int java.lang.String, java.lang.String);
public java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public int userId();
public int id();
public java.lang.String title();
public java.lang.String body();
}
Here you can see that there is not default constructor which is needed got Jackson. The constructor you used is a compact constructor,
public Post {
}
You can define a default/no args constructor as,
public record Post(int userId, int id, String title, String body) {
public Post() {
this(0,0, null, null);
}
}
But Jackson uses Getter and Setters to set values. So in short, you can not use Record for mapping the response.
EDIT as PSA: Jackson can properly serialize and deserialize records as of 2.12 which has been released.
Use the parameter names module for jackson, https://github.com/FasterXML/jackson-modules-java8/tree/master/parameter-names (make sure the compiler sets -parameters) or add `#JsonProperty("name") to each field in the record
add #JsonCreator to the constructor. I can't tell if the inheritance will work properly, so you might have to explicitly declare the constructor and annotate it.
If a public accessor method or (non-compact) canonical constructor is declared explicitly, then it only has the annotations which appear on it directly; nothing is propagated from the corresponding record component to these members.
From https://openjdk.java.net/jeps/384
So add
new ObjectMapper().registerModules(new ParameterNamesModule())
and try
#JsonCreator record Value(String x);
or something like
record Value(String x) {
#JsonCreator
public Value(String x) {
this.x = x;
}
}
or all the way to
record Value(#JsonProperty("x") String x) {
#JsonCreator
public Value(#JsonProperty("x") String x) {
this.x = x;
}
}
This is how I get immutable pojos with lombok and jackson to work, and I don't see why records wouldn't work under the same format. My setup is Jackson parameter names module, -parameters compiler flag for java 8 (I don't think this is required for like jdk9+), #JsonCreator on the constructor. Example of a real class working with this setup.
#Value
#AllArgsConstructor(onConstructor_ = #JsonCreator)
public final class Address {
private final String line1;
private final String line2;
private final String city;
private final String region;
private final String postalCode;
private final CountryCode country;
}
I have a DynamoDB table in which I have columns. Two of them will always have the same datatype while one column's datatype will vary. How can I unmarshall/marshall it when using DynamoDB. Below is my DTO.
private Integer id;
private String name;
private Object value;
It is not allowing directly to map "value" field and throwing an exception.
Please help me in this regard.
Declare the field as:-
#CustomObjectFormat(separator = " ")
public Object getValue() {
return value;
}
Sample CustomObjectFormat code:-
The below implementation uses toString() to convert everything to String and persist as String data type in DynamoDB database.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#DynamoDBTypeConverted(converter=CustomObjectFormat.Converter.class)
public #interface CustomObjectFormat {
String separator() default " ";
public static class Converter implements DynamoDBTypeConverter<String, Object> {
private final String separator;
public Converter(final Class<Currency> targetType, final CustomObjectFormat annotation) {
this.separator = annotation.separator();
}
public Converter() {
this.separator = "|";
}
#Override
public String convert(final Object o) {
return o.toString();
}
#Override
public Object unconvert(final String o) {
return o;
}
}
}
Mapper to save:-
DynamoDBMapper will invoke the custom convert and unconvert accordingly for save and retrieval.
dynamoDBMapper.save(accounts);
DynamoDBTypeConverted JavaDoc
I am trying to unmarshall an ArrayList of a generic class called Key.
the Key has setValue() method which recieves a generic parameter.
Key class
#XMLRootElement(name = "Key")
public class Key<T>{
#XMLElement(name = "Key")
public setKey(T value){
this.value = value
}
}
The specific ArrayList
#XMLElementWrapper(name = "Keys")
#XMLElement(name = "Key")
public setKeys(ArrayList<Key> keys){
this.keys = keys;
}
This part of the XML file
<Keys>
<Key>2</Key>
</Keys>
Running the code would create the ArrayList and WILL have a single Key object in it.
But the Key would be Null.
(Ive tried debugging and could notice that it does not call the setKey() setter of the class)
Anything to do with the fact it's generic?
Thanks in advance.
EDIT
In the past day ive debugged this alot, i can say now that the problem is with the fact that after instantiating the ArrayList, while creating each Key per Key Tag in the XML, the unmarshaller uses the Key's empty constructor and just NEVER calls the setter of it, therefore i have an ArrayList containing Keys which their 'value' data member is null.
Can anyone please explain what am I doing wrong? Why does the setter not getting called?
Thank you.
You are probably out of luck. How is the unmarshaller supposed to know that 2 is an integer and not a double or a long or a timestamp or some other class with a custom adapter that can parse 2 into itself.
The annotations you want are basically below (minus the #XmlJavaTypeAdapter which I will explain in a moment) but if you try and run that code without the adapter you will get a NullPointerException because JAXB cannot handle the #XmlValue annotation on an Object (which is how it treats T). The reason JAXB cannot handle it is because it has no way of knowing what the object is.
Now, if you have your own custom rules for determining the type of T (e.g. when coming from XML T is always an Integer or T is an Integer if it doesn't contain a '.' and a Double otherwise) then you can implement your own logic using an adapter which is what I've demonstrated below (I used the second rule).
#XmlRootElement(name="root")
public class SO {
private List<Key<?>> keys;
#XmlElementWrapper(name="Keys")
#XmlElement(name="Key")
public void setKeys(List<Key<?>> keys) {
this.keys = keys;
}
public List<Key<?>> getKeys() {
return keys;
}
#XmlType
public static class Key<T> {
private T val;
#XmlValue
#XmlJavaTypeAdapter(ToStringAdapter.class)
public void setKey(T val) {
this.val = val;
}
public String toString() {
return "Key(" + val + ")";
}
}
public static class ToStringAdapter extends XmlAdapter<String, Object> {
#Override
public Object unmarshal(String v) throws Exception {
if(v.contains(".")) {
return Double.parseDouble(v);
} else {
return Integer.parseInt(v);
}
}
#Override
public String marshal(Object v) throws Exception {
return v.toString(); //Will never be called anyway so you could also throw an exception here
}
}
private static final String XML_INT = "<root><Keys><Key>2</Key></Keys></root>";
private static final String XML_DOUBLE = "<root><Keys><Key>2.7</Key></Keys></root>";
public static void main(String [] args) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Key.class, SO.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
SO so = (SO) unmarshaller.unmarshal(new StringReader(XML_INT));
System.out.print(so.keys);
System.out.println(" " + so.keys.get(0).val.getClass().getSimpleName());
so = (SO) unmarshaller.unmarshal(new StringReader(XML_DOUBLE));
System.out.print(so.keys);
System.out.println(" " + so.keys.get(0).val.getClass().getSimpleName());
}
}
I have problems deserializing Enums that have multiple names for a value. Here is an example: Info is a Java class that inside has an enum with multiple names:
public class Info {
//...
private ContainerFormat format;
}
// ContainerFormat.java:
public enum ContainerFormat {
// ....
MP4("mp4", "mpeg4"),
UNKNOWN("null");
private String name;
private List<String> others;
ContainerFormat(String name) {
this.name = name;
}
/** The service does not always return the same String for output formats.
* This 'other' string fixes the deserialization issues caused by that.
*/
ContainerFormat(String name, String... others) {
this.name = name;
this.others = new ArrayList<String>();
for (String other : others) {
this.others.add(other);
}
}
#JsonValue
#Override
public String toString() {
return name;
}
public List<String> otherNames() {
return others;
}
#JsonCreator
public static ContainerFormat fromValue(String other) throws JsonMappingException {
for (ContainerFormat format : ContainerFormat.values()) {
if (format.toString().equalsIgnoreCase(other)) {
return format;
}
if (format.otherNames() != null && format.otherNames().contains(other)) {
return format;
}
}
return UNKNOWN;
}
}
The problem is when I deserialize something that contains "mpeg4" instead of mp4 I get this error:
com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of com.foo.ContainerFormat from String value 'mpeg4': value not one of declared Enum instance names
at [Source: N/A; line: -1, column: -1] (through reference chain: com.foo.Info["format"])
at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:55)
at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:650)
at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:85)
at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:20)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:375)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:98)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:308)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:2769)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1478)
at com.fasterxml.jackson.databind.ObjectMapper.treeToValue(ObjectMapper.java:1811)
Any pointers on how to fix this?
TIA
I found a good solution based on Florin's answer:
the correct configuration with jackson 2.7.0-rc2 (and probably also before)
private ObjectMapper createObjectMapper() {
final ObjectMapper mapper = new ObjectMapper();
// enable toString method of enums to return the value to be mapped
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
return mapper;
}
In your enum you just have to override the toString() method:
public enum EXAMPLE_TYPE {
START("start"),
MORE("more");
// the value which is used for matching
// the json node value with this enum
private final String value;
SectionType(final String type) {
value = type;
}
#Override
public String toString() {
return value;
}
}
You don't need any annotations or custom deserializers.
Get rid of String name and List<String> other and instead have just one field - List<String> names and serialize the single getter with #JsonValue
public enum ContainerFormat {
// ....
MP4("mp4", "mpeg4"),
UNKNOWN("null");
private List<String> names;
ContainerFormat(List<String> names) {
this.names = new ArrayList<String>(names);
}
#JsonValue
public List<String> getNames()
{
return this.names;
}
#JsonCreator
public static ContainerFormat getContainerFromValue(String value) throws JsonMappingException {
for (ContainerFormat format : ContainerFormat.values()) {
if(format.getValues().contains(value))
return format;
}
return UNKNOWN;
}
Alternatively, if you choose to keep your existing code, you could try annotating otherValues() with #JsonValue
Well, I found a workaround: one of these flags does the right thing and allows me to read that mpeg4 back in:
mapper.configure(org.codehaus.jackson.map.SerializationConfig.Feature.WRITE_NULL_PROPERTIES, false);
mapper.configure(org.codehaus.jackson.map.SerializationConfig.Feature.WRITE_ENUMS_USING_TO_STRING, true);
mapper.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.READ_ENUMS_USING_TO_STRING, true);
mapper.setPropertyNamingStrategy(org.codehaus.jackson.map.PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
mapper.setSerializationInclusion(org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_EMPTY);
mapper.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);