Spring custom validator class with MultipartFile class support does not get called/fired - spring

Sorry i couldn't find a better way to construct the question.
I am trying to validate on server side an array of multipartfile received with other information (e.g email, phone number etc) from an Ajax multipart/form-data post request.
I created 2 custom validator classes by implementing Spring Validator interface, UserFilesValidator to check that all received files are image files and are not above 2MB
and UserInfosValidator to check other user information e.g. phone number validate, email avalibility etc.
I registered the custom validator classes using #InitBinder.
Now when request is made, UserInfosValidator gets called and works fine but UserFilesValidator doesn't get called
Here is my code
Controller class
#Controller
public class UserController{
private final UserFilesValidator userFilesValidator;
private final UserInfosValidator userInfosValidator;
#Autowired
public UserController(UserFilesValidator userFilesValidator, UserInfosValidator userInfosValidator){
this.userFilesValidator = userFilesValidator;
this.userInfosValidator = userInfosValidator;
}
#InitBinder("userfiles")
public void formDataBinder1(WebDataBinder binder){
binder.addValidators(userFilesValidator);
}
#InitBinder("userinfos")
public void formDataBinder2(WebDataBinder binder){
binder.addValidators(userInfosValidator);
}
#RequestMapping(value = "/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseBody
public ResponseEntity<UserEntity> addUser(#RequestParam("userfiles") #Valid MultipartFile[] files,
#RequestPart("userinfos") #Valid UserEntity user){
//codes ...
}
}
Validator class
#Component
public class UserFilesValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MultipartFile[].class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MultipartFile[] files = (MultipartFile[]) target;
//validation codes ...
}
}
I excluded UserInfosValidator class since it works fine.
I also did not included global exception handler class (that's #ControllerAdvice annotated class)
since errors has to be registered in UserFilesValidator class which is not even getting called.
So please how can i get this to work? I know i can validate it in addUser method but I don't want to have validation codes in controller class
request handler method.

Related

How to unit test a multipart POST request with Spring MVC Test?

I am trying to create unit test for REST APi but having big trouble with the uploading excel method.
Here is the method on the controller side
#RestController()
#RequestMapping(path = "/upload")
#CrossOrigin(origins = "http://localhost:4200")
public class FileController {
#Autowired
FileService fileService;
#PostMapping(value = "/{managerId}/project/{projectId}")
public List<Task> importExcelFile(#RequestParam("file") MultipartFile files, #PathVariable int managerId,
#PathVariable int projectId) throws IOException, ParseException {
return fileService.getTasksFromExcel(files, managerId, projectId);
}
Whatever I try I get a lot of errors and evidently I don't really understand what I am supposed to do.
The main error I get is
current request is not a multipart request
You can do the following.
I just simplified your example a tiny bit.
So, here's the controller that returns the file size of the file it receives.
#RestController
#RequestMapping(path = "/upload")
public class FileController {
#PostMapping(value = "/file")
public ResponseEntity<Object> importExcelFile(#RequestParam("file") MultipartFile files) {
return ResponseEntity.ok(files.getSize());
}
}
and this one is the test of it. There is a class called MockMvc that Spring provides to easily unit test your controllers and controller advices. There is a method called multipart that you can use to simulate file upload cases.
class FileControllerTest {
private final MockMvc mockMvc = MockMvcBuilders
.standaloneSetup(new FileController())
.build();
#Test
#SneakyThrows
void importExcelFile() {
final byte[] bytes = Files.readAllBytes(Paths.get("TEST_FILE_URL_HERE"));
mockMvc.perform(multipart("/upload/file")
.file("file", bytes))
.andExpect(status().isOk())
.andExpect(content().string("2037")); // size of the test input file
}
}
Generally Multipart uploads can be tested via MockMultipartFile:
https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/file-upload-test.html

How can #Validated work on common method?

In Springboot project, when I try to add #Validated on controller method, it worked.But now I want to add it on a common method, then failed.
Try to add #Validated on controller method, it worked
public class TaskValidator {
private static final Logger logger = LogManager.getLogger("TaskValidatorLogger");
public void validateTest(#Validated Test test) {
logger.info("Validate: {}", test.getName());
}
public static void main(String[] args) {
new TaskValidator().validateTest(new Test());
}
}
#Data
public class Test {
#NotNull(message = "name can not be null")
private String name;
}
It should throw an MethodArgumentNotValidException but not.
Spring MVC has the ability to automatically validate #Controller
inputs. In previous versions it was up to the developer to manually
invoke validation logic.
In the controller methods, springboot automatically binds any validators to the model and invoke it when the data is bound to the object.
But in your case , you are trying to validate an object in which case , springboot might not be automatically binding your validator to your model and call the validator.So, in that case, you will need to manually bind the object to the validator.
or you can manually invoke the validator on a bean like :
#AutoWired
Validator validator;
...
validator.validate(book);

calling a method by class level annotated with #RequestMapping that includes an autowired class

I am trying to call a method that is annotated with #RequestMapping(signIn) through a class level (from method: authentication) like so:
#RequestMapping(value = /authenticate, method = RequestMethod.POST)
public #ResponseBody Response authentication(HttpServletRequest request)
{
UserController user = new UserController();
return user.signIn(request, null);
}
and my controller looks like:
#Autowired
private UserManager userManager;
#RequestMapping(value = /signin, method = RequestMethod.POST)
public #ResponseBody Response signIn(HttpServletRequest request) {
JsonObject json = Misc.parseJson(request);
String lang = Misc.getLang(request);
user.setEmail(Misc.getEmail(json));
user.setPassword(Misc.getEncryptedPassword(json));
return ResponseUtils.success(userManager.auth(user, lang));
}
user manager is annotated with #component:
#Component
public class UserManager {
public User auth(User user, String lang) {
....
return user;
}
}
Problem is when I call the method "signIn" and just new-up a UserController instance through "/authenticate" mapping, the UserManager becomes NULL. So now I'm assuming that autowiring doesn't work when it's done this way.
Is there any other way to call the signIn method? I would hate to copy paste an already existing code to another class just to get this to work...
Autowiering only works in spring managed bean. If you create a class with new keyword, it is not a spring managed bean and autowiering would not work.
You can try to autowire the class which contains the method which is annotated or better put the code in a service class which can be used by both methods.
It's not problem with #Autowired .There are two type of Annotation
firstly method base annotation and field level annotation. You just used field level annotation.Check your import class with "org.springframework.beans.factory.annotation.Autowired" or it can be problem with initiation of "UserManager"
I don't know why you not moving logic into separate Service classs, but try this:
UserController.java
public UserController(UserManager userManager) {
this.userManager = userManager;
}
and then inside controller where authentication resource method is located:
#Autowired UserManager userManager;
#RequestMapping(value = /authenticate, method = RequestMethod.POST)
public #ResponseBody Response authentication(HttpServletRequest request) {
UserController user = new UserController(userManager);
return user.signIn(request);
}
So in the end I just separated the logic instead. Though one solution that I tried and I could have used was to just add another mapping to the signIn method instead of adding a new method in the other class since the logic was similar. Still I opted for a separate logic instead since there were a lot of unnecessary code in the signIn method for my purpose.

Run Validation before #HandleBeforeCreate

I'm having some problem with validation with Spring Rest, the #HandleBeforeCreate event handler is running before the validation. I was expecting it to run after the validation.
In my test application I have a transaction, which has two fields to store the transaction value, one for the real transaction currency and another for the final value converted to the user currency. In my handle before create I'm dealing with that conversion, but I want the request to stop in the validator if the amount is null.
I could validate the resource in the event handler (I'm ready to handle a RepositoryConstraintViolationException), but it make me think on the point of using validator. It also seams a little inefficient that on every data rest request, spring loop through all validator checking if they support the object class.
Is validation on EventHandlers preferable than Validators (for performance reasons)? How can I force validator to run before EventHandlers?
*I'm using spring-boot 1.4.2.RELEASE
Validator
public class TransactionValidator extends SpringValidator<Transaction> {
#Override
public boolean supports(Class<?> clazz) {
return Transaction.class.equals(clazz) ;
}
#Override
public void validateObject(Transaction transaction, Errors errors) {
... validations ...
}
}
public abstract class SpringValidator<T> implements Validator {
#Override
public void validate(Object target, Errors errors) {
validateObject((T) target, errors);
}
protected abstract void validateObject(T target, Errors errors);
}
Event Handler
#Component
#RequiredArgsConstructor
#RepositoryEventHandler(Transaction.class)
public class TransacationEventHandler {
private final CurrencyUnitService currencyUnitService;
#HandleBeforeCreate
public void beforeCreate(Transaction transaction) {
adjustTransactionAmount(transaction);
}
#HandleBeforeSave
public void beforeSave(Transaction transaction) {
adjustTransactionAmount(transaction);
}
}
Edit
I checked the source code and the listeners are invoked in the following order:
Which make sense actually, using a BeforeCreateHandler is the only way to fix/change something in the Entity before running the Validator. I'm 100% open to inputs.
1. Use #Validated (did not test it)
You could do this:
#HandleBeforeCreate
public void beforeCreate(#Validated Transaction transaction) {
adjustTransactionAmount(transaction);
}
2. Otherwise - you may define the Validator through Java Configuration
You may follow this answer from #MathiasDpunkt (Spring data rest validation + exception mapper: confusing):
#Configuration
public class MyValidationConfiguration extends RepositoryRestConfigurerAdapter {
#Bean
#Primary
/**
* Create a validator to use in bean validation - primary to be able to autowire without qualifier
*/
Validator validator() {
return new LocalValidatorFactoryBean();
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener
validatingListener) {
Validator validator = validator();
//bean validation always before save and create
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
}

How to check security acess (#Secured or #PreAuthorize) before validation (#Valid) in my Controller?

here is my Controller code :
#PreAuthorize("hasRole('CREATE_USER')")
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public UserReturnRO createUser(#Valid #RequestBody UserRO userRO) throws BadParameterException{
return userService.createUser(userRO);
}
My need is when a client without the appropriate role tries to create a user, the controller responds "Not authorized" even if the data sent are not valid. Instead of that, if the client (without the appropriate role) tries to create a user with wrong data, my controller responds with the #Valid message (ex : "password cannot be empty"), while I want it responds "not authorized".
In the PreAuthorized Interface we can find this sentence :
Annotation for specifying a method access-control expression which will be evaluated to decide whether a method invocation is allowed or not.
but it seems that it's not the case.
You can not do this directly, since #Valid is processed before an actual method call and as a result before #PreAuthorize.
But what you can do instead is to inject BindingResult just right after your model (userRO) and in doing so - take control of validation process. Then check if BindingResult has some errors and if so return bad request response (similar to what spring does).
Example:
#ResponseBody
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
#PreAuthorize("hasRole('CREATE_USER')")
public ResponseEntity<?> createUser(#RequestBody #Valid UserRO userRO, BindingResult result) {
if (result.hasErrors()) {
return ResponseEntity.badRequest().body(result.getAllErrors());
}
return ResponseEntity.ok(userService.createUser(userRO));
}
As already stated, Spring Security's #PreAuthorize is method advice, which means that it does not get to participate until the method and its arguments have already been resolved.
Aside from the answer already given, there are a few ways to move authorization before argument resolution, instead.
Filter Security
First, Spring Security checks URLs before the request is mapped to a method. And since this is a #Controller, it's reasonable to suppose that you could instead map the request to the role at that level instead of #PreAuthorize:
http
.authorizeRequests()
.mvcMatchers(POST, "/somepath").hasRole("CREATE_USER")
Handler Interceptor
Second, Spring MVC does ship with limited support for checking authorities before parsing method arguments. For example, you can do:
#EnableWebMvc
public static class MvcConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
UserRoleAuthorizationInterceptor userRole =
new UserRoleAuthorizationInterceptor();
userRole.setAuthorizedRoles("CREATE_USER");
registry.addInterceptor(userRole);
}
}
This is much more basic than #PreAuthorize since it's a global setting, but I've included it for completeness.
Handler Interceptor, Part 2
Third (warning, some inelegance ahead), you can create your own HandlerInterceptor.
The flow is:
FilterSecurityInterceptor <== where .mvcMatchers(...).hasRole(...) lives
Then HandlerInterceptors
Then argument validation
Then MethodSecurityInterceptor <== where #PreAuthorize lives
So, your HandlerInterceptor would check before arguments are resolved. It doesn't have to be as involved as MethodSecurityInterceptor, though. It could, for example, simply be:
static class AuthorizationInterceptor extends HandlerInterceptorAdapter {
SecurityMetadataSource securityMetadataSource;
AccessDecisionManager accessDecisionManager;
#Override
public void preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
Authentication authenticated = (Authentication) request.getUserPrincipal();
MethodInvocation mi = convert(handler);
Collection<ConfigAttribute> attributes =
this.securityMetadataSource.getAttributes(mi);
// throws AccessDeniedException
this.accessDecisionManager.decide(authenticated, mi, attributes);
return true;
}
}
Then you wire it together with:
#EnableGlobalMethodSecurity(prePostEnabled = true)
static class MethodConfig extends GlobalMethodSecurityConfiguration {
#Bean
HandlerInterceptor preAuthorize() throws Exception {
return new AuthorizationInterceptor(
accessDecisionManager(), methodSecurityMetadataSource());
}
}
#EnableWebMvc
public static class MvcConfig implements WebMvcConfigurer {
#Autowired
AuthorizationInterceptor authorizationInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authorizationInterceptor);
}
}
It's inelegant because MethodSecurityInterceptor would still participate in authorized requests, which would ostensibly be the majority.

Resources