Skip to content

Commit

Permalink
Merge pull request #44 from Oh-my-class/enhanced_validation
Browse files Browse the repository at this point in the history
Enhanced validation
  • Loading branch information
Lezurex authored Nov 17, 2022
2 parents f16b51c + 71594c1 commit 70697a4
Show file tree
Hide file tree
Showing 45 changed files with 775 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
import com.ohmyclass.api.components.role.entity.Role;
import com.ohmyclass.api.components.role.repository.IRoleRepository;
import com.ohmyclass.api.components.user.entity.User;
import com.ohmyclass.api.components.user.repository.IUserRepository;
import com.ohmyclass.api.components.user.repository.UserRepository;
import com.ohmyclass.api.util.communication.Request;
import com.ohmyclass.api.util.communication.Response;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Optional;
Expand Down Expand Up @@ -39,7 +38,7 @@ public Response<Boolean> deleteRole(Request<Long> roleIdPayload) {
}

private final IRoleRepository roleRepository;
private final IUserRepository userRepository;
private final UserRepository userRepository;

@Override
public Response<Boolean> setRoleToUser(String username) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ public interface IUserController {
@PutMapping("/user")
@Operation(summary = "Edit user details in database")
@ApiResponse(responseCode = "200", description = "The updated user", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = UserOutDTO.class)) })
@Content(mediaType = "application/json", schema = @Schema(implementation = Boolean.class)) })
@ApiResponse(responseCode = "401", description = "Authentication failed")
@ApiResponse(responseCode = "500", description = "General server error")
Response<UserOutDTO> updateUser(@RequestBody Request<UserChangeInDTO> userChangeIn);
Response<Boolean> updateUser(@RequestBody UserChangeInDTO userChangeIn);

@Secured("ROLE_USER")
@DeleteMapping("/user")
Expand All @@ -73,5 +73,5 @@ public interface IUserController {
@Content(mediaType = "application/json", schema = @Schema(implementation = Boolean.class)) })
@ApiResponse(responseCode = "401", description = "Authentication failed")
@ApiResponse(responseCode = "500", description = "General server error")
Response<Boolean> deleteUser(@RequestBody Request<UserInDTO> user);
Response<Boolean> deleteUser(@RequestBody String username);
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ public Response<UserOutDTO> getUser(String username) {
}

@Override
public Response<UserOutDTO> updateUser(Request<UserChangeInDTO> userChangeIn) {
public Response<Boolean> updateUser(UserChangeInDTO userChangeIn) {
return new Response<>(userService.update(userChangeIn), ValidationResult.ok());
}

@Override
public Response<Boolean> deleteUser(Request<UserInDTO> user) {
return new Response<>(userService.delete(user), ValidationResult.ok());
public Response<Boolean> deleteUser(String username) {
return new Response<>(userService.delete(username), ValidationResult.ok());
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.ohmyclass.api.components.user.dto.in;

import com.ohmyclass.api.util.communication.InDTO;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserChangeInDTO extends UserInDTO {
public class UserChangeInDTO extends InDTO {

private String newEmail;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import java.util.Optional;

@Repository
public interface IUserRepository extends CrudRepository<User, Long> {
public interface UserRepository extends CrudRepository<User, Long> {

Optional<User> findById(Long id);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,29 @@
import com.ohmyclass.api.components.user.dto.in.UserChangeInDTO;
import com.ohmyclass.api.components.user.dto.in.UserInDTO;
import com.ohmyclass.api.components.user.dto.out.UserOutDTO;
import com.ohmyclass.api.exceptions.ApiException;
import com.ohmyclass.api.util.communication.Request;
import com.ohmyclass.api.util.communication.Response;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import java.util.Map;

public interface IUserService {

@Transactional(rollbackOn = ApiException.class)
Map<String, String> register(UserInDTO user);

void refreshToken(HttpServletRequest request, HttpServletResponse response);

UserOutDTO getUser(String username);

UserOutDTO update(Request<UserChangeInDTO> user);
@Transactional(rollbackOn = ApiException.class)
Boolean update(UserChangeInDTO user);

Boolean delete(Request<UserInDTO> user);
@Transactional(rollbackOn = ApiException.class)
Boolean delete(String username);

void passwordForgotten(HttpServletRequest request, HttpServletResponse response);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,149 +7,107 @@
import com.ohmyclass.api.components.user.dto.in.UserInDTO;
import com.ohmyclass.api.components.user.dto.out.UserOutDTO;
import com.ohmyclass.api.components.user.entity.User;
import com.ohmyclass.api.components.user.repository.IUserRepository;
import com.ohmyclass.api.components.user.repository.UserRepository;
import com.ohmyclass.api.components.user.service.crud.IUserService;
import com.ohmyclass.api.components.user.service.mapper.AUserMapper;
import com.ohmyclass.api.exceptions.ApiRequestException;
import com.ohmyclass.api.util.communication.Request;
import com.ohmyclass.api.components.user.service.mapper.UserMapper;
import com.ohmyclass.api.components.user.service.processors.UserChangeSubmissionProcessor;
import com.ohmyclass.api.components.user.service.processors.UserSubmissionProcessor;
import com.ohmyclass.api.exceptions.ApiException;
import com.ohmyclass.security.util.JwtTokenUtil;
import com.ohmyclass.util.validate.Validate;
import com.ohmyclass.util.validators.*;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;

import static org.springframework.http.HttpHeaders.AUTHORIZATION;

@Log4j2
@Component
@AllArgsConstructor
public class UserService implements IUserService {

private final IUserRepository userRepo;
private final UserRepository userRepository;

private final AUserMapper userMapper;

private final PasswordEncoder passwordEncoder;
private final UserMapper userMapper;

private final JwtTokenUtil tokenUtil;

@Override
@Transactional
public Map<String, String> register(UserInDTO inDTO) {
private final UserSubmissionProcessor userSubmissionProcessor;

Validate.notNull( "No Payload found", inDTO);
private final UserChangeSubmissionProcessor userChangeSubmissionProcessor;

if (userRepo.findByUsername(inDTO.getUsername()).isPresent())
throw new ApiRequestException("Username already exists");
@Override
public Map<String, String> register(UserInDTO inDTO) {

User user = userMapper.inDTOToEntity(inDTO);
user.addRole(new Role("ROLE_USER"));
Optional<User> user1 = saveUser(user);
userSubmissionProcessor.process(inDTO);

if (user1.isEmpty())
throw new ApiRequestException("Registration failed");
User persistedUser = userSubmissionProcessor.getPersistedEntity();

List<String> roles = user.getRoles().stream()
List<String> roles = persistedUser.getRoles().stream()
.map(Role::getName)
.collect(Collectors.toList());

return tokenUtil.generateNewTokenMap(user.getUsername(), "Registration", roles);
return tokenUtil.generateNewTokenMap(persistedUser.getUsername(), "Registration", roles);
}

@Override
public void refreshToken(HttpServletRequest request, HttpServletResponse response) {
String authorizationHeader = request.getHeader(AUTHORIZATION);

// Guard
if (!tokenUtil.isValidBearer(authorizationHeader)) {
String authorizationHeader = request.getHeader(AUTHORIZATION);

throw new ApiRequestException("Invalid bearer token.");
}
tokenUtil.validateBearer(authorizationHeader);

try {
DecodedJWT decodedJWT = tokenUtil.extractBearer.apply(authorizationHeader);
DecodedJWT decodedJWT = tokenUtil.extractBearer.apply(authorizationHeader);

if (tokenUtil.isTokenExpired(decodedJWT)) {
tokenUtil.validateTokenExpiration(decodedJWT);

throw new ApiRequestException("Token expired. Please login again");
}
User user = userRepository.findByUsername(decodedJWT.getSubject())
.orElseThrow(() -> new ApiException("User not found from token refresh"));

String newAccessToken = createNewAccessToken(request, decodedJWT);
String newAccessToken = tokenUtil.generateNewAccessToken(user);

try {
new ObjectMapper().writeValue(response.getOutputStream(), newAccessToken);

} catch (Exception e) {
throw new ApiRequestException("Refreshing token failed");
throw new ApiException("Refreshing token failed");
}

tokenUtil.addNewTokenToSecurity(user);
}

@Override
public UserOutDTO getUser(String username) {

Validate.notNull(username);
ObjectValidator.notNull("Supplied username was null", username);

return userMapper.entityToOutDTO(userRepo.findByUsername(username)
.orElseThrow(() -> new ApiRequestException("User not found")));
return userMapper.entityToOutDTO(userRepository.findByUsernameOrEmail(username, username)
.orElseThrow(() -> new ApiException("User not found")));
}

@Override
public UserOutDTO update(Request<UserChangeInDTO> userIn) {
return null;
}
public Boolean update(UserChangeInDTO userIn) {

@Override
public Boolean delete(Request<UserInDTO> userIn) {
return null;
return userChangeSubmissionProcessor.process(userIn).isOk();
}

@Override
public void passwordForgotten(HttpServletRequest request, HttpServletResponse response) {
}

private String createNewAccessToken(HttpServletRequest request, DecodedJWT decodedJWT) {

User user = userRepo.findByUsername(decodedJWT.getSubject())
.orElseThrow(() -> new ApiRequestException("User not found for token refresh"));

String subject = user.getUsername();
String issuer = request.getRequestURI();
List<String> rolesClaim = user.getRoles().stream()
.map(Role::getName)
.collect(Collectors.toList());
public Boolean delete(String username) {

String newAccessToken = tokenUtil.generateNewAccessToken(subject, issuer, rolesClaim);
ObjectValidator.notNull("Body can not be null", username);

List<SimpleGrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());

UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(user.getUsername(), null, authorities);
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new ApiException("User not found"));

SecurityContextHolder.getContext().setAuthentication(authenticationToken);
userRepository.delete(user);

return newAccessToken;
return userRepository.findByUsername(username).isEmpty();
}

private Optional<User> saveUser(User user) {

log.info("Saving user {}", user.getUsername());

user.setPassword(passwordEncoder.encode(user.getPassword()));

return Optional.of(userRepo.save(user));
@Override
public void passwordForgotten(HttpServletRequest request, HttpServletResponse response) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import com.ohmyclass.api.components.user.entity.User;
import com.sun.istack.NotNull;
import org.mapstruct.*;
import org.springframework.security.crypto.password.PasswordEncoder;

@Mapper(componentModel = "spring", uses = APreferencesMapper.class)
public abstract class AUserMapper {
public abstract class UserMapper {

@Mapping(source = "preferences", target = "preferencesOut")
public abstract UserOutDTO entityToOutDTO(@NotNull User user);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.ohmyclass.api.components.user.service.processors;

import com.ohmyclass.api.components.user.dto.in.UserChangeInDTO;
import com.ohmyclass.api.components.user.entity.User;
import com.ohmyclass.api.components.user.repository.UserRepository;
import com.ohmyclass.api.util.SubmissionProcessor;
import com.ohmyclass.api.components.user.service.validation.UserChangeSubmissionValidator;
import com.ohmyclass.api.exceptions.ApiException;
import com.ohmyclass.api.util.validation.ValidationResult;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class UserChangeSubmissionProcessor extends SubmissionProcessor<UserChangeInDTO> {

private final UserChangeSubmissionValidator userChangeSubmissionValidator;

private final UserRepository userRepository;

@Override
protected ValidationResult validate(UserChangeInDTO userIn) {
return userChangeSubmissionValidator.validate(userIn);
}

@Override
protected void persist(UserChangeInDTO userIn) {

User user = userRepository.findByUsername(userIn.resolveHeaderById("username"))
.orElseThrow(() -> new ApiException("User to edit not found"));

user.setEmail(userIn.getNewEmail());
user.setPassword(userIn.getNewPassword());

userRepository.save(user);
}

@Override
protected void prePersistOperations(UserChangeInDTO userIn) {
if (userRepository.findByEmail(userIn.getNewEmail()).isPresent()) {
throw new ApiException("Email already exists");
}
}

@Override
protected void postPersistOperations(UserChangeInDTO userIn) {
if (userRepository.findByEmailAndPassword(userIn.getNewEmail(), userIn.getNewPassword()).isEmpty()) {
throw new ApiException("User not updated correctly");
}
}
}
Loading

0 comments on commit 70697a4

Please sign in to comment.