From 48f7d84383d1a247dfb1a1941729f3447db7cff2 Mon Sep 17 00:00:00 2001 From: florian Date: Sun, 1 Sep 2019 13:27:45 +0200 Subject: [PATCH] Add signup route and change ids to UUID type. --- .../cerberus/controllers/UserController.java | 10 +- .../security/SecurityConfiguration.java | 3 +- .../org/cerberus/core/utils/RegexUtils.java | 89 ++++++++++++++++++ .../org/cerberus/core/utils/StringUtils.java | 94 +++++++++++++++++++ .../org/cerberus/entities/dto/SignUpDTO.java | 44 +++++++++ .../entities/persistence/AbstractEntity.java | 25 +++++ .../entities/persistence/Application.java | 21 +---- .../entities/persistence/ApplicationRole.java | 14 +-- .../persistence/ConfigurationFile.java | 16 +--- .../cerberus/entities/persistence/User.java | 19 +--- .../org/cerberus/mappers/SignUpMapper.java | 16 ++++ .../cerberus/repositories/UserRepository.java | 3 + .../org/cerberus/services/UserService.java | 22 ++++- .../cerberus/validators/SignUpValidator.java | 29 ++++++ 14 files changed, 349 insertions(+), 56 deletions(-) create mode 100755 src/main/java/org/cerberus/core/utils/RegexUtils.java create mode 100755 src/main/java/org/cerberus/core/utils/StringUtils.java create mode 100644 src/main/java/org/cerberus/entities/dto/SignUpDTO.java create mode 100644 src/main/java/org/cerberus/entities/persistence/AbstractEntity.java create mode 100644 src/main/java/org/cerberus/mappers/SignUpMapper.java create mode 100644 src/main/java/org/cerberus/validators/SignUpValidator.java diff --git a/src/main/java/org/cerberus/controllers/UserController.java b/src/main/java/org/cerberus/controllers/UserController.java index ddd7243..f90a1d0 100644 --- a/src/main/java/org/cerberus/controllers/UserController.java +++ b/src/main/java/org/cerberus/controllers/UserController.java @@ -1,6 +1,7 @@ package org.cerberus.controllers; import org.cerberus.core.config.security.CustomAuthenticationProvider; +import org.cerberus.entities.dto.SignUpDTO; import org.cerberus.entities.persistence.User; import org.cerberus.repositories.UserRepository; import org.cerberus.services.UserService; @@ -13,7 +14,6 @@ import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.util.Collections; @RestController @RequestMapping("/api/users") @@ -31,11 +31,17 @@ public class UserController { } @GetMapping("/disconnection") - public void disconnection(final HttpServletRequest request, final HttpServletResponse response) { + public void disconnection(HttpServletRequest request, HttpServletResponse response) { final Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if(auth != null) { new SecurityContextLogoutHandler().logout(request, response, auth); } response.setStatus(HttpServletResponse.SC_NO_CONTENT); } + + @PostMapping("/signup") + public void signUp(@RequestBody SignUpDTO inputData, HttpServletResponse response) { + userService.signUp(inputData); + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } } diff --git a/src/main/java/org/cerberus/core/config/security/SecurityConfiguration.java b/src/main/java/org/cerberus/core/config/security/SecurityConfiguration.java index 8189f9c..3891faa 100755 --- a/src/main/java/org/cerberus/core/config/security/SecurityConfiguration.java +++ b/src/main/java/org/cerberus/core/config/security/SecurityConfiguration.java @@ -50,7 +50,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { "/api/users/disconnection" ).permitAll() .antMatchers(POST, - "/api/users/login" + "/api/users/login", + "/api/users/signup" ).permitAll() .anyRequest().permitAll() .and() diff --git a/src/main/java/org/cerberus/core/utils/RegexUtils.java b/src/main/java/org/cerberus/core/utils/RegexUtils.java new file mode 100755 index 0000000..793e1f6 --- /dev/null +++ b/src/main/java/org/cerberus/core/utils/RegexUtils.java @@ -0,0 +1,89 @@ +package org.cerberus.core.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class RegexUtils { + + private static final String EMAIL_REGEX = "^.*@.*\\..{2,}$"; + private static final String LOWER_LETTERS_REGEX = ".*[a-z].*"; + private static final String UPPER_LETTERS_REGEX = ".*[A-Z].*"; + private static final String NUMBER_REGEX = ".*[0-9].*"; + private static final String SPECIAL_CHAR_REGEX = ".*\\W.*"; + private static final String NUMBER_ONLY_REGEX = "^[0-9]+$"; + private static final String PHONE_NUMBER_REGEX = "^(\\d{10}|\\d{2}(\\.\\d{2}){4})$"; + + // La portée "package" permet à la classe StringUtils d'utiliser les patterns + // suivants : + static final Pattern EMAIL_PATTERN; + static final Pattern LOWER_LETTERS_PATTERN; + static final Pattern UPPER_LETTERS_PATTERN; + static final Pattern NUMBER_PATTERN; + static final Pattern SPECIAL_CHAR_PATTERN; + static final Pattern NUMBER_ONLY_PATTERN; + static final Pattern PHONE_NUMBER_PATTERN; + + static { + EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); + LOWER_LETTERS_PATTERN = Pattern.compile(LOWER_LETTERS_REGEX); + UPPER_LETTERS_PATTERN = Pattern.compile(UPPER_LETTERS_REGEX); + NUMBER_PATTERN = Pattern.compile(NUMBER_REGEX); + SPECIAL_CHAR_PATTERN = Pattern.compile(SPECIAL_CHAR_REGEX); + NUMBER_ONLY_PATTERN = Pattern.compile(NUMBER_ONLY_REGEX); + PHONE_NUMBER_PATTERN = Pattern.compile(PHONE_NUMBER_REGEX); + } + + /** + * Chekcs if {@code pString} corresponds to an email address. + * + * @param pString + * The string which should be an email address. + * @return {@code true} if {@link pString} corresponds to an email address, + * {@code false} otherwise. + */ + public static boolean isEmail(final String pString) { + return EMAIL_PATTERN.matcher(pString).find(); + } + + /** + * Replace the sequences of {@code pString} matched by the {@code pRegex} + * with the {@code pReplacingString}. + * + * @param pString + * The string to update. + * @param pRegex + * The regex to match the sentences to replace. + * @param pReplacingString + * The string to replace the sentences which match with the + * regex. + * @return The new string. + */ + public static String replaceSequence(final String pString, + final String pRegex, final String pReplacingString) { + return Pattern.compile(pRegex).matcher(pString) + .replaceAll(pReplacingString); + } + + /** + * Checks if {@code pString} corresponds to a number. + * + * @param pString + * The string which should be a number. + * @return {@code true} if {@code pString} corresponds to a number, + * {@code false} otherwise. + */ + public static boolean isNumber(final String pString) { + return NUMBER_ONLY_PATTERN.matcher(pString).find(); + } + + public static String getGroup(final String regex, final int numeroGroupe, final String chaine) { + final Pattern pattern = Pattern.compile(regex); + final Matcher matcher = pattern.matcher(chaine); + matcher.find(); + return matcher.group(numeroGroupe); + } + + public static boolean isPhoneNumber(final String pString) { + return PHONE_NUMBER_PATTERN.matcher(pString).find(); + } +} \ No newline at end of file diff --git a/src/main/java/org/cerberus/core/utils/StringUtils.java b/src/main/java/org/cerberus/core/utils/StringUtils.java new file mode 100755 index 0000000..15174d0 --- /dev/null +++ b/src/main/java/org/cerberus/core/utils/StringUtils.java @@ -0,0 +1,94 @@ +package org.cerberus.core.utils; + +import org.mindrot.jbcrypt.BCrypt; + +/** + * Generic methods about {@link String} class. + * + * @author takiguchi + * + */ +public final class StringUtils { + public static final int PASSWORD_SALT_LENGTH = 10; + + /** + * Indicate if {@code pString} is null or just composed of spaces. + * + * @param pString + * The string to test. + * @return {@code true} if {@code pString} is null or just composed of + * spaces, {@code false} otherwise. + */ + public static boolean isNull(final String chaine) { + return chaine == null || chaine.trim().isEmpty(); + } + + /** + * Hash the password given into parameters. + * + * @param pPassword The password to hash. + * @return The password hashed. + */ + public static String hashPassword(final String pPassword) { + return hashString(pPassword, PASSWORD_SALT_LENGTH); + } + + public static String hashString(final String pString, final int pSalt) { + return BCrypt.hashpw(pString, BCrypt.gensalt(pSalt)); + } + + /** + * Compare the password and the hashed string given into parameters. + * + * @param pPassword + * The password to compare to the hashed string. + * @param pHashToCompare + * The hashed string to compare to the password. + * @return {@code true} if the password matches to the hashed string. + */ + public static boolean compareHash(final String pPassword, final String pHashToCompare) { + return BCrypt.checkpw(pPassword, pHashToCompare); + } + + /** + * Concatenate the parameters to form just one single string. + * + * @param pArgs + * The strings to concatenate. + * @return The parameters concatenated. + */ + public static String concat(final Object... pArgs) { + final StringBuilder result = new StringBuilder(); + for (final Object arg : pArgs) { + result.append(arg); + } + return result.toString(); + } + + public static String printStrings(final String... pStrings) { + final StringBuilder result = new StringBuilder(); + for (int i = 0 ; i < pStrings.length ; i++) { + result.append(pStrings[i]); + if(i < pStrings.length - 1) { + result.append(","); + } + } + return result.toString(); + } + + public static boolean containLowercase(final String pString) { + return RegexUtils.LOWER_LETTERS_PATTERN.matcher(pString).find(); + } + + public static boolean containUppercase(final String pString) { + return RegexUtils.UPPER_LETTERS_PATTERN.matcher(pString).find(); + } + + public static boolean containNumber(final String pString) { + return RegexUtils.NUMBER_PATTERN.matcher(pString).find(); + } + + public static boolean containSpecialChar(final String pString) { + return RegexUtils.SPECIAL_CHAR_PATTERN.matcher(pString).find(); + } +} diff --git a/src/main/java/org/cerberus/entities/dto/SignUpDTO.java b/src/main/java/org/cerberus/entities/dto/SignUpDTO.java new file mode 100644 index 0000000..301ef87 --- /dev/null +++ b/src/main/java/org/cerberus/entities/dto/SignUpDTO.java @@ -0,0 +1,44 @@ +package org.cerberus.entities.dto; + +public class SignUpDTO { + + private String name; + + private String email; + + private String password; + + private String confirmPassword; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getConfirmPassword() { + return confirmPassword; + } + + public void setConfirmPassword(String confirmPassword) { + this.confirmPassword = confirmPassword; + } +} diff --git a/src/main/java/org/cerberus/entities/persistence/AbstractEntity.java b/src/main/java/org/cerberus/entities/persistence/AbstractEntity.java new file mode 100644 index 0000000..7323e66 --- /dev/null +++ b/src/main/java/org/cerberus/entities/persistence/AbstractEntity.java @@ -0,0 +1,25 @@ +package org.cerberus.entities.persistence; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.PrePersist; +import java.util.UUID; + +@Entity +abstract class AbstractEntity { + @Id + protected UUID id; + + @PrePersist + public void prePersist() { + id = UUID.randomUUID(); + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } +} diff --git a/src/main/java/org/cerberus/entities/persistence/Application.java b/src/main/java/org/cerberus/entities/persistence/Application.java index be07d70..35eaad4 100644 --- a/src/main/java/org/cerberus/entities/persistence/Application.java +++ b/src/main/java/org/cerberus/entities/persistence/Application.java @@ -1,20 +1,17 @@ package org.cerberus.entities.persistence; -import org.hibernate.annotations.Generated; -import org.hibernate.annotations.GenerationTime; import org.hibernate.annotations.Proxy; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.OneToMany; +import javax.persistence.Table; import java.util.List; @Entity @Table(name="application") @Proxy(lazy = false) -public class Application { - @Id - @Generated(GenerationTime.ALWAYS) - private String id; - +public class Application extends AbstractEntity { @Column(nullable = false) private String name; @@ -27,14 +24,6 @@ public class Application { @OneToMany(mappedBy = "application") private List administratorList; - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - public String getName() { return name; } diff --git a/src/main/java/org/cerberus/entities/persistence/ApplicationRole.java b/src/main/java/org/cerberus/entities/persistence/ApplicationRole.java index 7b2b3e9..af68bf4 100644 --- a/src/main/java/org/cerberus/entities/persistence/ApplicationRole.java +++ b/src/main/java/org/cerberus/entities/persistence/ApplicationRole.java @@ -3,8 +3,8 @@ package org.cerberus.entities.persistence; import org.cerberus.core.constant.Role; import javax.persistence.*; - import java.io.Serializable; +import java.util.UUID; import static javax.persistence.FetchType.LAZY; @@ -14,23 +14,23 @@ public class ApplicationRole { @Embeddable public static class ApplicationRoleId implements Serializable { @Column(name = "user_id") - private String userId; + private UUID userId; @Column(name = "application_id") - private String applicationId; + private UUID applicationId; - String getUserId() { + UUID getUserId() { return userId; } - void setUserId(String userId) { + void setUserId(UUID userId) { this.userId = userId; } - String getApplicationId() { + UUID getApplicationId() { return applicationId; } - void setApplicationId(String applicationId) { + void setApplicationId(UUID applicationId) { this.applicationId = applicationId; } } diff --git a/src/main/java/org/cerberus/entities/persistence/ConfigurationFile.java b/src/main/java/org/cerberus/entities/persistence/ConfigurationFile.java index 463499e..f4aeba3 100644 --- a/src/main/java/org/cerberus/entities/persistence/ConfigurationFile.java +++ b/src/main/java/org/cerberus/entities/persistence/ConfigurationFile.java @@ -1,7 +1,5 @@ package org.cerberus.entities.persistence; -import org.hibernate.annotations.Generated; -import org.hibernate.annotations.GenerationTime; import org.hibernate.annotations.Proxy; import javax.persistence.*; @@ -9,11 +7,7 @@ import javax.persistence.*; @Entity @Table(name="configuration_file") @Proxy(lazy = false) -public class ConfigurationFile { - @Id - @Generated(GenerationTime.ALWAYS) - private String id; - +public class ConfigurationFile extends AbstractEntity { @Column(nullable = false) private String path; @@ -21,14 +15,6 @@ public class ConfigurationFile { @JoinColumn(name = "application_id") private Application application; - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - public String getPath() { return path; } diff --git a/src/main/java/org/cerberus/entities/persistence/User.java b/src/main/java/org/cerberus/entities/persistence/User.java index 8f52813..711e0e8 100644 --- a/src/main/java/org/cerberus/entities/persistence/User.java +++ b/src/main/java/org/cerberus/entities/persistence/User.java @@ -4,18 +4,17 @@ import org.hibernate.annotations.Generated; import org.hibernate.annotations.GenerationTime; import org.hibernate.annotations.Proxy; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.OneToMany; +import javax.persistence.Table; import java.time.LocalDate; import java.util.List; @Entity @Table(name="`user`") @Proxy(lazy = false) -public class User { - @Id - @Generated(GenerationTime.ALWAYS) - private String id; - +public class User extends AbstractEntity { @Column(nullable = false) private String name; @@ -35,14 +34,6 @@ public class User { @OneToMany(mappedBy = "user") private List applicationRoleList; - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - public String getName() { return name; } diff --git a/src/main/java/org/cerberus/mappers/SignUpMapper.java b/src/main/java/org/cerberus/mappers/SignUpMapper.java new file mode 100644 index 0000000..5f3ecb2 --- /dev/null +++ b/src/main/java/org/cerberus/mappers/SignUpMapper.java @@ -0,0 +1,16 @@ +package org.cerberus.mappers; + +import org.cerberus.entities.dto.SignUpDTO; +import org.cerberus.entities.persistence.User; +import org.springframework.stereotype.Component; + +@Component +public class SignUpMapper { + public User toUser(SignUpDTO inputData) { + User user = new User(); + user.setName(inputData.getName()); + user.setEmail(inputData.getEmail()); + user.setPassword(inputData.getPassword()); + return user; + } +} diff --git a/src/main/java/org/cerberus/repositories/UserRepository.java b/src/main/java/org/cerberus/repositories/UserRepository.java index 5be3b1b..50c96c4 100644 --- a/src/main/java/org/cerberus/repositories/UserRepository.java +++ b/src/main/java/org/cerberus/repositories/UserRepository.java @@ -15,4 +15,7 @@ public interface UserRepository extends JpaRepository { @Query("SELECT ar FROM ApplicationRole ar JOIN FETCH ar.application WHERE ar.user.email = :email") List getApplicationRolesByEmail(@Param("email") String email); + + @Query(value = "SELECT EXISTS(SELECT id FROM \"user\" WHERE email = :email)", nativeQuery = true) + boolean isEmailAlreadyExists(@Param("email") String email); } diff --git a/src/main/java/org/cerberus/services/UserService.java b/src/main/java/org/cerberus/services/UserService.java index ccb1f53..0fe8feb 100644 --- a/src/main/java/org/cerberus/services/UserService.java +++ b/src/main/java/org/cerberus/services/UserService.java @@ -3,9 +3,12 @@ package org.cerberus.services; import org.cerberus.core.constant.Role; import org.cerberus.core.config.security.CustomAuthenticationProvider; import org.cerberus.core.exceptions.BadRequestException; +import org.cerberus.entities.dto.SignUpDTO; import org.cerberus.entities.persistence.ApplicationRole; import org.cerberus.entities.persistence.User; +import org.cerberus.mappers.SignUpMapper; import org.cerberus.repositories.UserRepository; +import org.cerberus.validators.SignUpValidator; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -18,10 +21,17 @@ import java.util.stream.Collectors; @Service public class UserService { private CustomAuthenticationProvider authenticationProvider; + private SignUpMapper signUpMapper; + private SignUpValidator signUpValidator; private UserRepository userRepository; - public UserService(CustomAuthenticationProvider authenticationProvider, UserRepository userRepository) { + public UserService(CustomAuthenticationProvider authenticationProvider, + SignUpMapper signUpMapper, + SignUpValidator signUpValidator, + UserRepository userRepository) { this.authenticationProvider = authenticationProvider; + this.signUpMapper = signUpMapper; + this.signUpValidator = signUpValidator; this.userRepository = userRepository; } @@ -50,4 +60,14 @@ public class UserService { .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); } + + public void signUp(SignUpDTO inputData) { + signUpValidator.checkAllAttributsConstraints(inputData); + + if(userRepository.isEmailAlreadyExists(inputData.getEmail())) { + throw new BadRequestException("Email is already assigned to another user."); + } + + userRepository.save(signUpMapper.toUser(inputData)); + } } diff --git a/src/main/java/org/cerberus/validators/SignUpValidator.java b/src/main/java/org/cerberus/validators/SignUpValidator.java new file mode 100644 index 0000000..8e2fd71 --- /dev/null +++ b/src/main/java/org/cerberus/validators/SignUpValidator.java @@ -0,0 +1,29 @@ +package org.cerberus.validators; + +import org.cerberus.core.exceptions.BadRequestException; +import org.cerberus.core.utils.RegexUtils; +import org.cerberus.core.utils.StringUtils; +import org.cerberus.entities.dto.SignUpDTO; +import org.cerberus.entities.persistence.User; +import org.springframework.stereotype.Component; + +@Component +public class SignUpValidator { + + public void checkAllAttributsConstraints(SignUpDTO inputData) { + if(StringUtils.isNull(inputData.getName()) + || StringUtils.isNull(inputData.getEmail()) + || StringUtils.isNull(inputData.getPassword()) + || StringUtils.isNull(inputData.getConfirmPassword())) { + throw new BadRequestException("Please fill up all required fields."); + } + + if(!RegexUtils.isEmail(inputData.getEmail())) { + throw new BadRequestException("Email address is incorrect."); + } + + if(!inputData.getPassword().equals(inputData.getConfirmPassword())) { + throw new BadRequestException("Both filled password aren't identical."); + } + } +}