From d3ac95e8f4e336f61ea5e6fed18d8be85cadd0b7 Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Thu, 24 Oct 2019 12:12:42 +0530 Subject: [PATCH] Implemented primitive rest validation. A better implementation to be done later. --- .../controller/AdminRESTController.java | 4 +- .../controller/ChatMessageController.java | 56 +++++++++++++++++-- ...oller.java => RegistrationController.java} | 5 +- .../org/ros/chatto/dto/ChatMessageDTO.java | 12 +++- .../org/ros/chatto/dto/MessageCipherDTO.java | 27 +++++++++ .../org/ros/chatto/dto/ReencryptionDTO.java | 9 +++ .../ros/chatto/dto/UserRegistrationDTO.java | 8 +-- .../java/org/ros/chatto/error/ErrorModel.java | 16 ++++++ .../org/ros/chatto/error/ErrorResponse.java | 17 ++++++ .../java/org/ros/chatto/model/ChatUser.java | 6 +- .../org/ros/chatto/service/ChatService.java | 4 +- .../ros/chatto/service/ChatServiceImpl.java | 13 ++++- .../ros/chatto/service/UserServiceImpl.java | 4 ++ chatto/src/main/resources/static/js/admin.js | 1 - .../resources/templates/fragments/head.html | 4 +- .../resources/templates/registration.html | 19 +++---- 16 files changed, 171 insertions(+), 34 deletions(-) rename chatto/src/main/java/org/ros/chatto/controller/{RegisterController.java => RegistrationController.java} (91%) create mode 100644 chatto/src/main/java/org/ros/chatto/error/ErrorModel.java create mode 100644 chatto/src/main/java/org/ros/chatto/error/ErrorResponse.java diff --git a/chatto/src/main/java/org/ros/chatto/controller/AdminRESTController.java b/chatto/src/main/java/org/ros/chatto/controller/AdminRESTController.java index e33aec3..4256471 100644 --- a/chatto/src/main/java/org/ros/chatto/controller/AdminRESTController.java +++ b/chatto/src/main/java/org/ros/chatto/controller/AdminRESTController.java @@ -7,6 +7,8 @@ import java.time.format.DateTimeFormatterBuilder; import java.util.Date; import java.util.List; +import javax.validation.Valid; + import org.ros.chatto.dto.ChatMessageDTO; import org.ros.chatto.dto.ReencryptionDTO; import org.ros.chatto.model.ChatMessage; @@ -34,7 +36,7 @@ public class AdminRESTController { @PostMapping(value = "/post/re-encrypt", consumes = { "application/json" }) @ResponseBody - public ResponseEntity reencryptMessages(@RequestBody List reencryptionDTOs, + public ResponseEntity reencryptMessages(@RequestBody @Valid List reencryptionDTOs, Principal principal) { if (reencryptionDTOs.size() > 0) { chatService.reencryptMessages(reencryptionDTOs); diff --git a/chatto/src/main/java/org/ros/chatto/controller/ChatMessageController.java b/chatto/src/main/java/org/ros/chatto/controller/ChatMessageController.java index 0c3ae55..dacd7cd 100644 --- a/chatto/src/main/java/org/ros/chatto/controller/ChatMessageController.java +++ b/chatto/src/main/java/org/ros/chatto/controller/ChatMessageController.java @@ -6,16 +6,23 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; + +import javax.validation.Valid; import org.ros.chatto.dto.ChatMessageDTO; import org.ros.chatto.dto.MessageCipherDTO; -import org.ros.chatto.dto.ReencryptionDTO; -import org.ros.chatto.model.MessageCipher; +import org.ros.chatto.error.ErrorModel; +import org.ros.chatto.error.ErrorResponse; import org.ros.chatto.service.ChatService; import org.ros.chatto.service.UserService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -23,6 +30,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @RestController @@ -36,14 +44,52 @@ public class ChatMessageController { @PostMapping(value = "/post/message", consumes = { "application/json" }) @ResponseBody - public ResponseEntity newMessage(@RequestBody ChatMessageDTO chatMessageDTO, Principal principal) { + public ResponseEntity newMessage(@RequestBody @Valid ChatMessageDTO chatMessageDTO, + Principal principal) { +// if (bindingResult.hasErrors()) { +// +//// return new ResponseEntity>(bindingResult.getFieldErrors(),HttpStatus.BAD_REQUEST); +// return new ResponseEntity(handleException(bindingResult), HttpStatus.BAD_REQUEST); +// } MessageCipherDTO messageCipher = chatMessageDTO.getMessageCipher(); String fromUser = principal.getName(); String toUser = chatMessageDTO.getToUser(); System.out.println("Message cipher = " + messageCipher); - chatService.saveNewMessage(fromUser, toUser, messageCipher); - return new ResponseEntity(HttpStatus.OK); + chatMessageDTO = chatService.saveNewMessage(fromUser, toUser, messageCipher); + HttpHeaders responseHeader = new HttpHeaders(); + return new ResponseEntity(chatMessageDTO, responseHeader, HttpStatus.CREATED); } + + /** + * Method that check against {@code @Valid} Objects passed to controller endpoints + * + * @param exception + * @return a {@code ErrorResponse} + * @see com.aroussi.util.validation.ErrorResponse + */ + @ExceptionHandler(value=MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleException(MethodArgumentNotValidException exception) { + + List errorMessages = exception.getBindingResult().getFieldErrors().stream() + .map(err -> new ErrorModel(err.getField(), err.getRejectedValue(), err.getDefaultMessage())) + .distinct() + .collect(Collectors.toList()); + return ErrorResponse.builder().errorMessage(errorMessages).build(); + } + + @ExceptionHandler(value=MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleException(BindingResult bindingResult) { + + List errorMessages = bindingResult.getFieldErrors().stream() + .map(err -> new ErrorModel(err.getField(), err.getRejectedValue(), err.getDefaultMessage())) + .distinct() + .collect(Collectors.toList()); + return ErrorResponse.builder().errorMessage(errorMessages).build(); + } + + @GetMapping(value = "/get/messages/{userName}") @ResponseBody diff --git a/chatto/src/main/java/org/ros/chatto/controller/RegisterController.java b/chatto/src/main/java/org/ros/chatto/controller/RegistrationController.java similarity index 91% rename from chatto/src/main/java/org/ros/chatto/controller/RegisterController.java rename to chatto/src/main/java/org/ros/chatto/controller/RegistrationController.java index cb84a38..02ed450 100644 --- a/chatto/src/main/java/org/ros/chatto/controller/RegisterController.java +++ b/chatto/src/main/java/org/ros/chatto/controller/RegistrationController.java @@ -25,8 +25,9 @@ public class RegistrationController { } @PostMapping("/perform_registration") - public String performRegistration(Model model, - @ModelAttribute("userRegistrationDTO") @Valid UserRegistrationDTO userRegistrationDTO, BindingResult bindingResult) { + public String performRegistration( + @ModelAttribute("userRegistrationDTO") @Valid UserRegistrationDTO userRegistrationDTO, + BindingResult bindingResult) { if (bindingResult.hasErrors()) { System.out.println("Input has errors!"); return "registration"; diff --git a/chatto/src/main/java/org/ros/chatto/dto/ChatMessageDTO.java b/chatto/src/main/java/org/ros/chatto/dto/ChatMessageDTO.java index 4d97214..ffe81c4 100644 --- a/chatto/src/main/java/org/ros/chatto/dto/ChatMessageDTO.java +++ b/chatto/src/main/java/org/ros/chatto/dto/ChatMessageDTO.java @@ -2,13 +2,21 @@ package org.ros.chatto.dto; import java.util.Date; -import org.ros.chatto.model.MessageCipher; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import lombok.Data; @Data public class ChatMessageDTO { - private String toUser, fromUser; + @NotBlank(message = "Username should not be blank") + @Pattern(regexp = "^[A-Za-z0-9]+$", message = "Username must be alphanumeric") + @Size(max=15) + private String toUser; + @Pattern(regexp = "^[A-Za-z0-9]+$", message = "Username must be alphanumeric") + @Size(max=15) + private String fromUser; private MessageCipherDTO messageCipher; private Date messageTime; } diff --git a/chatto/src/main/java/org/ros/chatto/dto/MessageCipherDTO.java b/chatto/src/main/java/org/ros/chatto/dto/MessageCipherDTO.java index 731a2bf..80bef33 100644 --- a/chatto/src/main/java/org/ros/chatto/dto/MessageCipherDTO.java +++ b/chatto/src/main/java/org/ros/chatto/dto/MessageCipherDTO.java @@ -1,5 +1,11 @@ package org.ros.chatto.dto; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; @@ -8,18 +14,39 @@ import lombok.Setter; @Getter @Setter public class MessageCipherDTO { + @Pattern(regexp = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") // regex for base64 + @NotBlank private String iv; + @Max(1) + @NotBlank private int v; + @Max(1_000_000) + @Min(1_000) + @NotBlank @JsonProperty("iter") private int iterations; + @Max(256) + @Min(128) @JsonProperty("ks") private int keySize; + @Max(256) + @Min(128) @JsonProperty("ts") private int tagSize; + @Pattern(regexp = "^[A-Za-z0-9]+$") // alphabetic + @NotBlank private String mode; + @Pattern(regexp = "^[A-Za-z0-9]+$") private String adata; + @Pattern(regexp = "^[A-Za-z0-9]+$") + @NotBlank private String cipher; + @Pattern(regexp = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") + @NotBlank private String salt; @JsonProperty("ct") + @Pattern(regexp = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") + @NotBlank + @Size(max = 2000, min = 1) private String cipherText; } diff --git a/chatto/src/main/java/org/ros/chatto/dto/ReencryptionDTO.java b/chatto/src/main/java/org/ros/chatto/dto/ReencryptionDTO.java index c70cdd8..b85e82f 100644 --- a/chatto/src/main/java/org/ros/chatto/dto/ReencryptionDTO.java +++ b/chatto/src/main/java/org/ros/chatto/dto/ReencryptionDTO.java @@ -2,13 +2,22 @@ package org.ros.chatto.dto; import java.util.Date; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + import org.ros.chatto.model.MessageCipher; import lombok.Data; @Data public class ReencryptionDTO { + @NotBlank(message = "Username should not be blank") + @Pattern(regexp = "^[A-Za-z0-9]+$", message = "Username must be alphanumeric") + @Size(max=15) private String toUser, fromUser; + @NotBlank + @Size(max=600) private MessageCipher messageCipher; private Date messageTime; } diff --git a/chatto/src/main/java/org/ros/chatto/dto/UserRegistrationDTO.java b/chatto/src/main/java/org/ros/chatto/dto/UserRegistrationDTO.java index 4677f4c..f049238 100644 --- a/chatto/src/main/java/org/ros/chatto/dto/UserRegistrationDTO.java +++ b/chatto/src/main/java/org/ros/chatto/dto/UserRegistrationDTO.java @@ -7,13 +7,13 @@ import javax.validation.constraints.Size; public class UserRegistrationDTO { @Size(min = 4, max = 10, message = "Username must be between 4 and 10 characters") - @NotBlank(message = " Username should not be blank") + @NotBlank(message = "Username should not be blank") @Pattern(regexp = "^[A-Za-z0-9]+$", message = "Username must be alphanumeric") private String userName; @Transient - @Size(min = 4, max = 75, message = "Username must be between 4 and 75 characters") - @NotBlank(message = " Password should not be blank") - @Pattern(regexp = "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message = "Invalid password format") + @Size(min = 4, max = 75, message = "Password must be between 4 and 75 characters") + @NotBlank(message = "Password should not be blank") +// @Pattern(regexp = "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message = "Invalid password format") private String password; public String getUserName() { diff --git a/chatto/src/main/java/org/ros/chatto/error/ErrorModel.java b/chatto/src/main/java/org/ros/chatto/error/ErrorModel.java new file mode 100644 index 0000000..519edda --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/error/ErrorModel.java @@ -0,0 +1,16 @@ +package org.ros.chatto.error; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ErrorModel{ + private String fieldName; + private Object rejectedValue; + private String messageError; + + +} \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/error/ErrorResponse.java b/chatto/src/main/java/org/ros/chatto/error/ErrorResponse.java new file mode 100644 index 0000000..42f9792 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/error/ErrorResponse.java @@ -0,0 +1,17 @@ +package org.ros.chatto.error; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErrorResponse { + private List errorMessage; + +} \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/model/ChatUser.java b/chatto/src/main/java/org/ros/chatto/model/ChatUser.java index 30afad8..6bc48e0 100644 --- a/chatto/src/main/java/org/ros/chatto/model/ChatUser.java +++ b/chatto/src/main/java/org/ros/chatto/model/ChatUser.java @@ -28,12 +28,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @Entity @Table(name = "users") @EntityListeners(AuditingEntityListener.class) -@JsonIgnoreProperties(value = { "password"}, allowGetters = false) +@JsonIgnoreProperties(value = { "password" }, allowGetters = false) public class ChatUser { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) -// @SequenceGenerator(name="user_generator", sequenceName = "user_seq", allocationSize=50) +// @SequenceGenerator(name="user_generator", sequenceName = "user_seq", allocationSize=50) //mysql does not support sequence id generator @Column(name = "user_id") private int userID; @Column(name = "name") @@ -47,7 +47,7 @@ public class ChatUser { @JsonBackReference // private Set userRoles = new HashSet(); private Set userRoles; - + public int getUserID() { return userID; } diff --git a/chatto/src/main/java/org/ros/chatto/service/ChatService.java b/chatto/src/main/java/org/ros/chatto/service/ChatService.java index 2fc9c67..e1d7106 100644 --- a/chatto/src/main/java/org/ros/chatto/service/ChatService.java +++ b/chatto/src/main/java/org/ros/chatto/service/ChatService.java @@ -3,6 +3,8 @@ package org.ros.chatto.service; import java.util.Date; import java.util.List; +import javax.validation.Valid; + import org.ros.chatto.dto.ChatMessageDTO; import org.ros.chatto.dto.MessageCipherDTO; import org.ros.chatto.dto.ReencryptionDTO; @@ -10,7 +12,7 @@ import org.ros.chatto.model.ChatMessage; import org.springframework.data.domain.PageRequest; public interface ChatService { - public void saveNewMessage(String fromUser, String toUser, MessageCipherDTO messageCipherDTO); + public @Valid ChatMessageDTO saveNewMessage(String fromUser, String toUser, MessageCipherDTO messageCipherDTO); public List getAllMessages(String fromUser, String toUser); diff --git a/chatto/src/main/java/org/ros/chatto/service/ChatServiceImpl.java b/chatto/src/main/java/org/ros/chatto/service/ChatServiceImpl.java index 841a49f..e5de4f6 100644 --- a/chatto/src/main/java/org/ros/chatto/service/ChatServiceImpl.java +++ b/chatto/src/main/java/org/ros/chatto/service/ChatServiceImpl.java @@ -1,5 +1,6 @@ package org.ros.chatto.service; +import java.sql.SQLException; import java.util.Date; import java.util.List; import java.util.stream.Collectors; @@ -34,18 +35,26 @@ public class ChatServiceImpl implements ChatService { @Autowired MyConversionService myConversionService; - public void saveNewMessage(String fromUserName, String toUserName, MessageCipherDTO messageCipherDTO) { + @Transactional + public ChatMessageDTO saveNewMessage(String fromUserName, String toUserName, MessageCipherDTO messageCipherDTO) { MessageCipher messageCipher = myConversionService.convertToMessageCipher(messageCipherDTO); ChatUser fromUser = userRepository.findByUserName(fromUserName); ChatUser toUser = userRepository.findByUserName(toUserName); + +// if(fromUser ==null || toUser == null) +// { +// System.out.println("User is null"); +// throw new SQLException(); +// } ChatMessage chatMessage = new ChatMessage(); messageCipher = messageCipherRepository.save(messageCipher); chatMessage.setMessageCipher(messageCipher); chatMessage.setFromUser(fromUser); chatMessage.setToUser(toUser); - chatMessageRepository.save(chatMessage); + chatMessage = chatMessageRepository.save(chatMessage); + return myConversionService.convertToChatMessageDTO(chatMessage); } diff --git a/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java b/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java index 646b663..d4f86e9 100644 --- a/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java +++ b/chatto/src/main/java/org/ros/chatto/service/UserServiceImpl.java @@ -2,6 +2,8 @@ package org.ros.chatto.service; import java.util.List; +import javax.transaction.Transactional; + import org.ros.chatto.dto.UserRegistrationDTO; import org.ros.chatto.model.ChatUser; import org.ros.chatto.model.Role; @@ -31,6 +33,7 @@ public class UserServiceImpl implements UserService{ UserRepositoryCustom userRepositoryCustom; @Override + @Transactional public void saveChatUser(ChatUser user) { // TODO Auto-generated method stub ChatUser changedUser = userRepository.save(user); @@ -41,6 +44,7 @@ public class UserServiceImpl implements UserService{ } @Override + @Transactional public void registerUser(UserRegistrationDTO userRegistrationDTO) { // TODO Auto-generated method stub ChatUser user = new ChatUser(); diff --git a/chatto/src/main/resources/static/js/admin.js b/chatto/src/main/resources/static/js/admin.js index 2bebe55..531df39 100644 --- a/chatto/src/main/resources/static/js/admin.js +++ b/chatto/src/main/resources/static/js/admin.js @@ -62,7 +62,6 @@ function handleChangePassphraseForm() { messageCiphers.push(messageCipherNewObj); // let messageCipherJson = JSON.stringify(messageCipherNewObj); - let fromUser = sessionStorage.getItem('username'); let chatMessageDTO = { "toUser": user, "fromUser": username, diff --git a/chatto/src/main/resources/templates/fragments/head.html b/chatto/src/main/resources/templates/fragments/head.html index 2ab0f22..596d97a 100644 --- a/chatto/src/main/resources/templates/fragments/head.html +++ b/chatto/src/main/resources/templates/fragments/head.html @@ -16,11 +16,11 @@ - + diff --git a/chatto/src/main/resources/templates/registration.html b/chatto/src/main/resources/templates/registration.html index 3db5e2a..9f8844e 100644 --- a/chatto/src/main/resources/templates/registration.html +++ b/chatto/src/main/resources/templates/registration.html @@ -17,7 +17,7 @@
-
+
@@ -36,22 +36,19 @@
-->
-

Register

+

Register

-
-
- - - -
+ + Username must be alphanumeric + +
- - - + +