Implemented primitive rest validation.

A better implementation to be done later.
This commit is contained in:
Rohan Sircar 2019-10-24 12:12:42 +05:30
parent 97091123b9
commit d3ac95e8f4
16 changed files with 171 additions and 34 deletions

View File

@ -7,6 +7,8 @@ import java.time.format.DateTimeFormatterBuilder;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.validation.Valid;
import org.ros.chatto.dto.ChatMessageDTO; import org.ros.chatto.dto.ChatMessageDTO;
import org.ros.chatto.dto.ReencryptionDTO; import org.ros.chatto.dto.ReencryptionDTO;
import org.ros.chatto.model.ChatMessage; import org.ros.chatto.model.ChatMessage;
@ -34,7 +36,7 @@ public class AdminRESTController {
@PostMapping(value = "/post/re-encrypt", consumes = { "application/json" }) @PostMapping(value = "/post/re-encrypt", consumes = { "application/json" })
@ResponseBody @ResponseBody
public ResponseEntity<ReencryptionDTO> reencryptMessages(@RequestBody List<ReencryptionDTO> reencryptionDTOs, public ResponseEntity<ReencryptionDTO> reencryptMessages(@RequestBody @Valid List<ReencryptionDTO> reencryptionDTOs,
Principal principal) { Principal principal) {
if (reencryptionDTOs.size() > 0) { if (reencryptionDTOs.size() > 0) {
chatService.reencryptMessages(reencryptionDTOs); chatService.reencryptMessages(reencryptionDTOs);

View File

@ -6,16 +6,23 @@ import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeFormatterBuilder;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.ros.chatto.dto.ChatMessageDTO; import org.ros.chatto.dto.ChatMessageDTO;
import org.ros.chatto.dto.MessageCipherDTO; import org.ros.chatto.dto.MessageCipherDTO;
import org.ros.chatto.dto.ReencryptionDTO; import org.ros.chatto.error.ErrorModel;
import org.ros.chatto.model.MessageCipher; import org.ros.chatto.error.ErrorResponse;
import org.ros.chatto.service.ChatService; import org.ros.chatto.service.ChatService;
import org.ros.chatto.service.UserService; import org.ros.chatto.service.UserService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; 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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@ -36,14 +44,52 @@ public class ChatMessageController {
@PostMapping(value = "/post/message", consumes = { "application/json" }) @PostMapping(value = "/post/message", consumes = { "application/json" })
@ResponseBody @ResponseBody
public ResponseEntity<ChatMessageDTO> newMessage(@RequestBody ChatMessageDTO chatMessageDTO, Principal principal) { public ResponseEntity<?> newMessage(@RequestBody @Valid ChatMessageDTO chatMessageDTO,
Principal principal) {
// if (bindingResult.hasErrors()) {
//
//// return new ResponseEntity<List<FieldError>>(bindingResult.getFieldErrors(),HttpStatus.BAD_REQUEST);
// return new ResponseEntity<ErrorResponse>(handleException(bindingResult), HttpStatus.BAD_REQUEST);
// }
MessageCipherDTO messageCipher = chatMessageDTO.getMessageCipher(); MessageCipherDTO messageCipher = chatMessageDTO.getMessageCipher();
String fromUser = principal.getName(); String fromUser = principal.getName();
String toUser = chatMessageDTO.getToUser(); String toUser = chatMessageDTO.getToUser();
System.out.println("Message cipher = " + messageCipher); System.out.println("Message cipher = " + messageCipher);
chatService.saveNewMessage(fromUser, toUser, messageCipher); chatMessageDTO = chatService.saveNewMessage(fromUser, toUser, messageCipher);
return new ResponseEntity<ChatMessageDTO>(HttpStatus.OK); HttpHeaders responseHeader = new HttpHeaders();
return new ResponseEntity<ChatMessageDTO>(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<ErrorModel> 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<ErrorModel> 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}") @GetMapping(value = "/get/messages/{userName}")
@ResponseBody @ResponseBody

View File

@ -25,8 +25,9 @@ public class RegistrationController {
} }
@PostMapping("/perform_registration") @PostMapping("/perform_registration")
public String performRegistration(Model model, public String performRegistration(
@ModelAttribute("userRegistrationDTO") @Valid UserRegistrationDTO userRegistrationDTO, BindingResult bindingResult) { @ModelAttribute("userRegistrationDTO") @Valid UserRegistrationDTO userRegistrationDTO,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
System.out.println("Input has errors!"); System.out.println("Input has errors!");
return "registration"; return "registration";

View File

@ -2,13 +2,21 @@ package org.ros.chatto.dto;
import java.util.Date; 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; import lombok.Data;
@Data @Data
public class ChatMessageDTO { 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 MessageCipherDTO messageCipher;
private Date messageTime; private Date messageTime;
} }

View File

@ -1,5 +1,11 @@
package org.ros.chatto.dto; 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 com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter; import lombok.Getter;
@ -8,18 +14,39 @@ import lombok.Setter;
@Getter @Getter
@Setter @Setter
public class MessageCipherDTO { 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; private String iv;
@Max(1)
@NotBlank
private int v; private int v;
@Max(1_000_000)
@Min(1_000)
@NotBlank
@JsonProperty("iter") @JsonProperty("iter")
private int iterations; private int iterations;
@Max(256)
@Min(128)
@JsonProperty("ks") @JsonProperty("ks")
private int keySize; private int keySize;
@Max(256)
@Min(128)
@JsonProperty("ts") @JsonProperty("ts")
private int tagSize; private int tagSize;
@Pattern(regexp = "^[A-Za-z0-9]+$") // alphabetic
@NotBlank
private String mode; private String mode;
@Pattern(regexp = "^[A-Za-z0-9]+$")
private String adata; private String adata;
@Pattern(regexp = "^[A-Za-z0-9]+$")
@NotBlank
private String cipher; private String cipher;
@Pattern(regexp = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$")
@NotBlank
private String salt; private String salt;
@JsonProperty("ct") @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; private String cipherText;
} }

View File

@ -2,13 +2,22 @@ package org.ros.chatto.dto;
import java.util.Date; 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 org.ros.chatto.model.MessageCipher;
import lombok.Data; import lombok.Data;
@Data @Data
public class ReencryptionDTO { 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; private String toUser, fromUser;
@NotBlank
@Size(max=600)
private MessageCipher messageCipher; private MessageCipher messageCipher;
private Date messageTime; private Date messageTime;
} }

View File

@ -7,13 +7,13 @@ import javax.validation.constraints.Size;
public class UserRegistrationDTO { public class UserRegistrationDTO {
@Size(min = 4, max = 10, message = "Username must be between 4 and 10 characters") @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") @Pattern(regexp = "^[A-Za-z0-9]+$", message = "Username must be alphanumeric")
private String userName; private String userName;
@Transient @Transient
@Size(min = 4, max = 75, message = "Username must be between 4 and 75 characters") @Size(min = 4, max = 75, message = "Password must be between 4 and 75 characters")
@NotBlank(message = " Password should not be blank") @NotBlank(message = "Password should not be blank")
@Pattern(regexp = "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message = "Invalid password format") // @Pattern(regexp = "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message = "Invalid password format")
private String password; private String password;
public String getUserName() { public String getUserName() {

View File

@ -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;
}

View File

@ -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<ErrorModel> errorMessage;
}

View File

@ -28,12 +28,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@Entity @Entity
@Table(name = "users") @Table(name = "users")
@EntityListeners(AuditingEntityListener.class) @EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = { "password"}, allowGetters = false) @JsonIgnoreProperties(value = { "password" }, allowGetters = false)
public class ChatUser { public class ChatUser {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @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") @Column(name = "user_id")
private int userID; private int userID;
@Column(name = "name") @Column(name = "name")
@ -47,7 +47,7 @@ public class ChatUser {
@JsonBackReference @JsonBackReference
// private Set<UserRole> userRoles = new HashSet<UserRole>(); // private Set<UserRole> userRoles = new HashSet<UserRole>();
private Set<UserRole> userRoles; private Set<UserRole> userRoles;
public int getUserID() { public int getUserID() {
return userID; return userID;
} }

View File

@ -3,6 +3,8 @@ package org.ros.chatto.service;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.validation.Valid;
import org.ros.chatto.dto.ChatMessageDTO; import org.ros.chatto.dto.ChatMessageDTO;
import org.ros.chatto.dto.MessageCipherDTO; import org.ros.chatto.dto.MessageCipherDTO;
import org.ros.chatto.dto.ReencryptionDTO; import org.ros.chatto.dto.ReencryptionDTO;
@ -10,7 +12,7 @@ import org.ros.chatto.model.ChatMessage;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
public interface ChatService { public interface ChatService {
public void saveNewMessage(String fromUser, String toUser, MessageCipherDTO messageCipherDTO); public @Valid ChatMessageDTO saveNewMessage(String fromUser, String toUser, MessageCipherDTO messageCipherDTO);
public List<ChatMessageDTO> getAllMessages(String fromUser, String toUser); public List<ChatMessageDTO> getAllMessages(String fromUser, String toUser);

View File

@ -1,5 +1,6 @@
package org.ros.chatto.service; package org.ros.chatto.service;
import java.sql.SQLException;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -34,18 +35,26 @@ public class ChatServiceImpl implements ChatService {
@Autowired @Autowired
MyConversionService myConversionService; 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); MessageCipher messageCipher = myConversionService.convertToMessageCipher(messageCipherDTO);
ChatUser fromUser = userRepository.findByUserName(fromUserName); ChatUser fromUser = userRepository.findByUserName(fromUserName);
ChatUser toUser = userRepository.findByUserName(toUserName); ChatUser toUser = userRepository.findByUserName(toUserName);
// if(fromUser ==null || toUser == null)
// {
// System.out.println("User is null");
// throw new SQLException();
// }
ChatMessage chatMessage = new ChatMessage(); ChatMessage chatMessage = new ChatMessage();
messageCipher = messageCipherRepository.save(messageCipher); messageCipher = messageCipherRepository.save(messageCipher);
chatMessage.setMessageCipher(messageCipher); chatMessage.setMessageCipher(messageCipher);
chatMessage.setFromUser(fromUser); chatMessage.setFromUser(fromUser);
chatMessage.setToUser(toUser); chatMessage.setToUser(toUser);
chatMessageRepository.save(chatMessage); chatMessage = chatMessageRepository.save(chatMessage);
return myConversionService.convertToChatMessageDTO(chatMessage);
} }

View File

@ -2,6 +2,8 @@ package org.ros.chatto.service;
import java.util.List; import java.util.List;
import javax.transaction.Transactional;
import org.ros.chatto.dto.UserRegistrationDTO; import org.ros.chatto.dto.UserRegistrationDTO;
import org.ros.chatto.model.ChatUser; import org.ros.chatto.model.ChatUser;
import org.ros.chatto.model.Role; import org.ros.chatto.model.Role;
@ -31,6 +33,7 @@ public class UserServiceImpl implements UserService{
UserRepositoryCustom userRepositoryCustom; UserRepositoryCustom userRepositoryCustom;
@Override @Override
@Transactional
public void saveChatUser(ChatUser user) { public void saveChatUser(ChatUser user) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
ChatUser changedUser = userRepository.save(user); ChatUser changedUser = userRepository.save(user);
@ -41,6 +44,7 @@ public class UserServiceImpl implements UserService{
} }
@Override @Override
@Transactional
public void registerUser(UserRegistrationDTO userRegistrationDTO) { public void registerUser(UserRegistrationDTO userRegistrationDTO) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
ChatUser user = new ChatUser(); ChatUser user = new ChatUser();

View File

@ -62,7 +62,6 @@ function handleChangePassphraseForm() {
messageCiphers.push(messageCipherNewObj); messageCiphers.push(messageCipherNewObj);
// let messageCipherJson = JSON.stringify(messageCipherNewObj); // let messageCipherJson = JSON.stringify(messageCipherNewObj);
let fromUser = sessionStorage.getItem('username');
let chatMessageDTO = { let chatMessageDTO = {
"toUser": user, "toUser": user,
"fromUser": username, "fromUser": username,

View File

@ -16,11 +16,11 @@
<link th:href="@{/css/master.css}" href="../static/css/master.css" rel="stylesheet"> <link th:href="@{/css/master.css}" href="../static/css/master.css" rel="stylesheet">
<link th:href="@{/css/colors.css}" href="../static/css/colors.css" rel="stylesheet"> <link th:href="@{/css/colors.css}" href="../static/css/colors.css" rel="stylesheet">
</link>
<script th:inline="javascript"> <script th:inline="javascript">
var hostAddress = window.location.host; var hostAddress = window.location.host;
/* var hostAddress2 = [[#{test.bindAddress}]]; */ /* var hostAddress2 = [[#{test.bindAddress}]]; */
console.log("hostname" + window.location.host); // console.log("hostname" + window.location.host);
</script> </script>
<meta charset="UTF-8"> <meta charset="UTF-8">

View File

@ -17,7 +17,7 @@
<div class="row"> <div class="row">
<div class="col-sm py-5"> <div class="col-sm py-5">
<!-- <h4 class="display-4 text-center py-2">Chat</h4> --> <!-- <h4 class="display-4 text-center py-2">Chat</h4> -->
<div class="card text-white bg-primary mb-3 text-center card-form rounded mx-auto" id="login-card"> <div class="card text-white bg-primary mb-3 card-form rounded mx-auto" id="login-card">
<div class="card-body rounded"> <div class="card-body rounded">
<!-- <h4 class="card-title">Chat</h4> --> <!-- <h4 class="card-title">Chat</h4> -->
@ -36,22 +36,19 @@
</div> --> </div> -->
<div class="card-text"> <div class="card-text">
<h2 class="card-title">Register</h2> <h2 class="card-title text-center">Register</h2>
<form action="#" th:action="@{/perform_registration}" th:object=${userRegistrationDTO} method="POST"> <form action="#" th:action="@{/perform_registration}" th:object=${userRegistrationDTO} method="POST">
<div class="form-group"> <div class="form-group">
<label>Enter username: </label> <label>Enter username: </label>
<div th:classappend="${#fields.hasErrors('userName')} ? 'input-icon right' : ''"> <input th:classappend="${#fields.hasErrors('userName')} ? 'is-invalid' : ''" class="form-control" th:field="*{userName}" type="text" name="username" required>
<i th:if="${#fields.hasErrors('userName')}" class="fa fa-exclamation tooltips" data-original-title="please enter a valid username" data-container="body"></i> <small class="form-text">Username must be alphanumeric</small>
<input class="form-control" th:field="*{userName}" type="text" name="username" id="username" required> <span th:if="${#fields.hasErrors('userName')}" class="help-block text-danger" th:errors="*{userName}"></span>
<span th:if="${#fields.hasErrors('userName')}" class="help-block" th:errors="*{userName}"></span>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Enter password: </label> <label>Enter password: </label>
<i th:if="${#fields.hasErrors('password')}" class="fa fa-exclamation tooltips" data-original-title="please enter a valid first name" data-container="body"></i> <input th:classappend="${#fields.hasErrors('password')} ? 'is-invalid' : ''" class="form-control" th:field="*{password}" type="password" name="password" id="password" required>
<input class="form-control" th:field="*{password}" type="password" name="password" id="password" required> <span th:if="${#fields.hasErrors('password')}" class="help-block text-danger" th:errors="*{password}"></span>
<span th:if="${#fields.hasErrors('password')}" class="help-block" th:errors="*{password}"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password-repeat">Repeat password: </label> <label for="password-repeat">Repeat password: </label>