Spring Rest Json Mapping on POST (Invalid Property) - java

This is a weird one for me. I've done the entities and the controllers and the form validation before, but I'm confused on this error.
So backstory. This is spring-boot w/Hibernate, connecting to a PostgreSQL Db. What I am attempting to do, is map a POST request to creating a resource. I'm trying to do this with pure JSON. I've been able to achieve this before.
The error in question is...
Invalid property 'Test' of bean class [com.example.api.entities.forms.OrganizationRegistrationForm]: Bean property 'Test' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
The request body, as it is in Postman is...
{
"organizationName":"Test",
"employees":10
}
The OrganizationRegistrationForm class it's complaining about...
public class OrganizationRegistrationForm {
#NotEmpty
private String organizationName = "";
#NotNull
private int employees;
private JsonNode contactInfo;
private JsonNode locationInfo;
public String getOrganizationName() {
return organizationName;
}
public void setOrganizationName(String name) {
this.organizationName = name;
}
public int getEmployees() {
return employees;
}
public void setEmployees(int employees) {
this.employees = employees;
}
public JsonNode getContactInfo() {
return contactInfo;
}
public void setContactInfo(JsonNode contactInfo) {
this.contactInfo = contactInfo;
}
public JsonNode getLocationInfo() {
return locationInfo;
}
public void setLocationInfo(JsonNode locationInfo) {
this.locationInfo = locationInfo;
}
}
And in case you need it, the request method...
#RequestMapping(value="/organization", method = RequestMethod.POST)
public Organization registerOrganization(#Valid #RequestBody OrganizationRegistrationForm form,
BindingResult bindingResult) throws Exception {
if(bindingResult.hasErrors()) {
LOGGER.error("The registration form entered has errors: {}", bindingResult.getAllErrors().toString());
throw new InvalidForm();
}
try {
Organization org = orgService.registerOrganization(form);
if(org!=null)
return org;
} catch(DataIntegrityViolationException e) {
bindingResult.reject("name.exists", "The supplied name is already in use");
}
throw new InvalidForm();
}
Although I'm guessing it doesn't even get that far. Originally the orginazationName field was called "name", but I changed it to see if maybe that was the issue.
The even weirder part for me is when I used this JSON object it worked. But created an organization named "organizationName".
{
"organizationName":"organizationName",
"employees":10
}
And one time it even complained that the invalid property was ''. As in empty. What am I doing wrong here?

I don't know how, or why. But for some reason the answer seemed to be in the OrganizationRegistrationFormValidator class that the binder uses.
The evil line in question was in validate(Object target, Errors errors) method...
ValidationUtils.rejectIfEmptyOrWhitespace(errors, target.getOrganizationName(), "name.empty", "Please enter a name");
Changing that line to a classic check worked.
if(target.getOrganizationName.isEmpty())
errors.reject("name.empty", "Please enter a name");
For documentation sake, anyone know why that happened? Are my api docs wrong when IntelliSense suggested that method signature?

I know this is old but I just stumbled over it:
to me ValidationUtils.rejectIfEmptyOrWhitespace(errors, target.getOrganizationName(), "name.empty", "Please enter a name"); looks wrong.
It should be:
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "organizationName", "name.empty", "Please enter a name");
The second attribute is the Field Name, not its content. ValidationUtils will take that name and transform it to the standard getter (getOrganizationName in that case) to retrieve its value and validate that.
This is why it tells you ther is no property named Test. Because there is none.

Related

Patch udpates and Validate RequestBody

I have been studying spring boot for a few weeks.
I am building a simple api using hibernate + jpa with a mysql database.
I have a resource call TvShowReminderResponseDTO :
public class TvShowReminderResponseDTO {
// Attributes
private Integer idTvShowReminder;
private User user;
private UserTvShow userTvShow;
private TvShowDetailsResponseDTO tvShowDetailsResponseDTO;
private Boolean completed;
private Integer currentSeason;
private Integer currentEpisode;
private Integer personalRating;
// rest of the code omittedfor brevity
}
In my controller i have a basic update PATCH endpoint, that receives the id of the tv show reminder (entity) that is stored in my database and also i receive a TvShowReminderPatchDTO with the information i want to update:
PatchDTO and Controller:
public class TvShowReminderPatchDTO {
// Attributes
private Optional<Boolean> completed;
private Optional<Integer> currentSeason;
private Optional<Integer> currentEpisode;
private Optional<Integer> personalRating;
// rest of the code omittedfor brevity
}
#PatchMapping("/{idTvShowReminder}")
public void updateTvShowReminder(#RequestBody #Valid TvShowReminderPatchDTO tvShowReminderToUpdate,
#PathVariable Integer idTvShowReminder){
tvShowReminderService.updateTvShowReminder(tvShowReminderToUpdate,idTvShowReminder);
}
Also I have my service method that is in charge of searching the TvShowReminder entity by its id, and then update the information we get from the client.
public void updateTvShowReminder(TvShowReminderPatchDTO tvShowReminderToUpdate, Integer idTvShowReminder) {
Optional<TvShowReminder> tvShowReminder = getTvShowReminder(idTvShowReminder);
TvShowReminder currentTvShowReminder = tvShowReminder.get();
if(tvShowReminderToUpdate.getCompleted() != null) {
if (tvShowReminderToUpdate.getCompleted().isPresent()) {
currentTvShowReminder.setCompleted(tvShowReminderToUpdate.getCompleted().get());
} else {
currentTvShowReminder.setCompleted(null);
}
}
if(tvShowReminderToUpdate.getCurrentSeason() != null) {
if (tvShowReminderToUpdate.getCurrentSeason().isPresent()) {
currentTvShowReminder.setCurrentSeason(tvShowReminderToUpdate.getCurrentSeason().get());
} else {
currentTvShowReminder.setCurrentSeason(null);
}
}
if(tvShowReminderToUpdate.getCurrentEpisode() != null) {
if (tvShowReminderToUpdate.getCurrentEpisode().isPresent()) {
currentTvShowReminder.setCurrentEpisode(tvShowReminderToUpdate.getCurrentEpisode().get());
} else {
currentTvShowReminder.setCurrentEpisode(null);
}
}
if(tvShowReminderToUpdate.getPersonalRating() != null) {
if (tvShowReminderToUpdate.getPersonalRating().isPresent()) {
currentTvShowReminder.setPersonalRating(tvShowReminderToUpdate.getPersonalRating().get());
} else {
currentTvShowReminder.setPersonalRating(null);
}
}
tvShowReminderRepository.save(currentTvShowReminder);
}
I have a question about the #valid annotation in the controller: i thought that it will check if the object that we send from postman for example is of type TvShowReminderPatchDTO , but i can send an entire different object and the controller will start its excecution, and the TvShowReminderPatchDTO will have all its attributes in NULL.
Whats the best way to check if the request body its in fact a TvShowReminderPatchDTO ?
I want to validate if the object we get from the Request is an instance of the TvShowReminderPatchDTO, and if not, throw an Exception.
The method that is doing the PATCH is working but its very ugly, I use optional as attributes in the TvShowReminderPatchDTO , so i can distinguish if the client wants to set a NULL (send an attribute with a null value ) or if the attribute was ommited (it does not appear on the request body) so we dont need to do anything, meaning we dont update it.
Can you guys recommend a better way to do this or improve the existing code?
Add some required fields using #NotNull annotation in your dto to help Spring understand which attributes should be present in your type
Don't use Optional. There is already JsonNullable for this purpose
public class TvShowReminderPatchDTO
{
#NotNull
private JsonNullable<Boolean> completed = JsonNullable.undefined();
}
And in controller method:
if (dto.getCompleted().isPresent()) {
object.setCompleted(dto.getCompleted().get());
}
That's it, no null-checks required, just set the value

DRYing up controller - method that returns entity or redirects (in Java, Spring)

I have a controller that has a few methods that get an optional of entity from service, checks if is present and proceeds with some other actions or redirects with message "Entity not found".
It looks like that:
#GetMapping("action")
public String method(#PathVariable Long id,
final RedirectAttributes redirectAttributes){
Optional<Entity> eOpt = entityService.findById(id);
if(eOpt.isEmpty()){
alertHandler.set(redirectAttributes, Status.ENTITY_NOT_FOUND);
return "redirect:/entity/list"
}
Entity e = eOpt.get();
// other actions that are using e
return "view-name";
}
The six lines repeat in a few methods and for different entities too. Is there a way to assign it to some private method? The only thing I came up with is using a private method like:
private Optional<Entity> getEntityOpt(Long id){
Optional<Entity> eOpt = entityService.findById(id);
if(eOpt.isEmpty()){
alertHandler.set(redirectAttributes, Status.ENTITY_NOT_FOUND);
}
return Optional.empty();
}
This only saves me one line in mapped methods, so I don't have to set up alert message. Otherwise I still have to check again if the Optional is empty to redirect it.
So I guess the question really is - can I set up the private method to either return entity or redirect like:
Entity e = getEntityOrRedirect(Long id);
or maybe you have different ways to handle that problem. Or maybe it is what it is and you have to repeat yourself...
You may treat empty Optional as an exceptional situation.
In that case you may provide your own RuntimeException containing path to redirect.
public class EntityNotFoundException extends RuntimeException {
private final String fallbackView;
public EntityNotFoundException(final String fallbackView) {
this.fallbackView = fallbackView;
}
public String getFallbackView() {
return fallbackView;
}
Then provide a method annotated with #ExceptionHandler to your controller class (or if the situation is common for multiple controllers then provide such method to class annotated with #ControllerAdvice). Your exception handler should catch just defined exception and do a redirect.
#ExceptionHandler(EntityNotFoundException.class)
public String redirectOnEntityNotFoundException(final EntityNotFoundException exception,
final RedirectAttributes redirectAttributes) {
alertHandler.set(redirectAttributes, Status.ENTITY_NOT_FOUND);
return exception.getFallbackView();
}
Finally you achieved some kind of getEntityOrRedirect. Now you may use the above setup as following:
#GetMapping("action")
public String method(#PathVariable Long id){
Entity e = entityService.findById(id)
.orElseThrow(() -> new EntityNotFoundException("redirect:/entity/list"));
// other actions that are using e
return "view-name";
}
Code not tested so apologize for typos in advance.
Note I believe it would work for Spring >= 4.3.5 as otherwise RedirectAttributes wouldn't be resolved for #ExceptionHandler (as stated here)

How to fix Mass Assignment: Insecure Binder Configuration (API Abuse, Structural) in java

I have a Controller class with the below two methods for finding a doctors (context changed). Getting the
Mass Assignment: Insecure Binder Configuration (API Abuse, Structural) error on both methods.
#Controller
#RequestMapping(value = "/findDocSearch")
public class Controller {
#Autowired
private IFindDocService findDocService;
#RequestMapping(value = "/byName", method = RequestMethod.GET)
#ResponseBody
public List<FindDocDTO> findDocByName(FindDocBean bean) {
return findDocService.retrieveDocByName(bean.getName());
}
#RequestMapping(value = "/byLoc", method = RequestMethod.GET)
#ResponseBody
public List<FindDocDTO> findDocByLocation(FindDocBean bean) {
return findDocService.retrieveDocByZipCode(bean.getZipcode(),
bean.getDistance());
}
}
and my Bean is :
public class FindDocBean implements Serializable {
private static final long serialVersionUID = -1212xxxL;
private String name;
private String zipcode;
private int distance;
#Override
public String toString() {
return String.format("FindDocBean[name: %s, zipcode:%s, distance:%s]",
name, zipcode, distance);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public int getDistance() {
return distance;
}
public void setDistance(int distance) {
this.distance = distance;
}
As per all the suggestions found so far, they are suggesting to restrict the bean with required parameters only by something like below :
final String[] DISALLOWED_FIELDS = new String[]{"bean.name", "bean.zipcode", };
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields(DISALLOWED_FIELDS);
But my problem is all the 3 parameters of the bean will be used in either of the method supplied on Controller.
Can someone please suggest some solution for this. Thanks in advance.
InitBinder can be used for methods. You can try this.
#InitBinder("findDocByName")
public void initBinderByName(WebDataBinder binder) {
binder.setDisallowedFields(new String[]{"distance","zipcode"});
}
#InitBinder("findDocByLocation")
public void initBinderByZipCode(WebDataBinder binder) {
binder.setDisallowedFields(new String[]{"distance","name"});
}
i was facing same issue, then i added below code in same rest controller class:
#InitBinder
public void populateCustomerRequest(WebDataBinder binder) {
binder.setDisallowedFields(new String[]{});
}
now its working fine for me and mass assignment issue was fixed.
Simple question - how your mapper can instantionate the bean? Here is answer / example. You can pass that data by query parameter, or in header. However that would be strange. Better is to have that methods with #QueryParam providing location, or name. That way it will be easier to protect your application.
As a side note, query has limited length, so if your search form is big and strange, #POST can be good idea, and that way you can pass all the data. For this, simple example that would be overkill.
This looks like an unfortunate false positive. The rule behind this error is made to avoid that properties present in an object but not intended to be (unvalidated) user input are accidentally populated from a web request. An example would be a POST request creating a resource. If the request handler takes the full resource object and fills only missing properties an malicious user could populate fields that she shouldn't be able to edit.
This case however does not match the scheme. You just use the same mechanism to capture your different arguments. Additionally populated properties will not even be read. In
GET http://yourhost/findDocSearch/byName?name=Abuse&zipCode=11111
the additional zipCode would just be ignored. Therefore the assumed risk is not present here.
To fix the warning, you could mark it as a false positive (if this is possible inside your setup). If that is not possible you could also just map the query parameters to method arguments directly. As you only have limited parameters that should not harm too much. If this is also no option you probably need to figure out the exact algorithm your code analysis uses to figure out what checks it will recognize. Unfortunately most scanners are only able to discover a limited set of ways to do input validation.

bindFromRequest validation null

I am new to the Java Play Framework and I'm trying to get the authentication to work. So I am following this tutorial: https://www.playframework.com/documentation/2.1.0/JavaGuide4
Here is my code:
public static Result authenticate()
{
Form<Login> loginForm = form(Login.class).bindFromRequest();
return ok(loginForm.toString());
}
public static class Login
{
public String email;
public String password;
public String validate()
{
return "VALIDATE "+email+password;
}
}
In the method autheticate() I can see the submitted values of the form, but the method validate() in the Login class does not see them (the variables are always null).. The output of loginForm.toString() contains:
Form(of=class controllers.Application$Login, data={email=asdf#asdf, password=asdf}, value=None, errors={=[ValidationError(,[VALIDATE nullnull],[])]})
As you can see, the data is received.. But in the validate method the data suddenly is equal to null. So how do I fix this?
You don't mention how you are calling validate() however I think this might do the trick, do something along the lines of:
public static Result authenticate() {
Form<Login> form = form(Login.class).bindFromRequest();
// handle errors
if (!form.hasErrors()) {
Login login = form.get();
Logger.debug(login.validate());
} else {
// bad request
}
}
This works for me.
Method validate in your model should return null if you think that validation has passed, otherwise you should return error message text. Then you need to check form if it contains error by "hasGlobalError" method. globalError is filled when validate() method returns String instead of null. But in your case you should use some model field annotations - https://www.playframework.com/documentation/2.3.x/api/java/play/data/validation/Constraints.html.
If you want to check if form fails on those - then you use "hasErrors" method.
public static class Login {
#Constraints.Email
public String email;
#Constraints.MinLength(value = 6)
public String password;
}
Such model will check if provided emails is really email and if password is longer or equal 6 characters.
ps. Do not use toString on template, you should use render()

Play framework sometimes needs getters and setters, sometimes it does not

I started a simple webapp based on play. After a bit of refactoring the login-form stopped working. I used an entity-bean with simple public fields. I moved it from one controller to another while refactoring and of cause corrected the references. It always told me I'm an invalid user.
During debugging I've found that the fields aren't set anymore. However, what really confused me: I manually added getters and setters to the public fields and suddenly it worked again. I've done now quite a bit of research why it works in the default-controller called "Application" but not in my own one called "Registration".
There isn't much code involved, here a few points:
public class RegistrationLogin extends Controller {
public static class Login {
#Required
public String email;
#Required
public String password;
public String validate() {
/* here is the interesting part, when I call "form.hasErrors" in
authenticateLogin and this validate-method gets called, email and
password both are null. If I create getters and setters they are set correctly */
if (User.authenticate(email, password) == null) {
return "Invalid user or password";
}
return null;
}
}
public static Result authenticateLogin() {
Form<Login> loginForm = form(Login.class).bindFromRequest("email", "password");
String title = "Login";
if (loginForm.hasErrors()) {
return badRequest(login.render(title,loginForm));
} else {
session().clear();
session("email", loginForm.get().email);
return redirect(
routes.Application.show(Ebean.find(User.class).where().ieq("email",loginForm.get().email).findUnique().getName())
);
}
}
When I had Login defined in Application (the default-controller which is generated when you start a project) it worked with just the fields.
Whats the origin of this behavior? Any hint might be helpful.

Categories