Add signup route and change ids to UUID type.

This commit is contained in:
2019-09-01 13:27:45 +02:00
parent 9c59d08af7
commit 48f7d84383
14 changed files with 349 additions and 56 deletions

View File

@@ -1,6 +1,7 @@
package org.cerberus.controllers; package org.cerberus.controllers;
import org.cerberus.core.config.security.CustomAuthenticationProvider; import org.cerberus.core.config.security.CustomAuthenticationProvider;
import org.cerberus.entities.dto.SignUpDTO;
import org.cerberus.entities.persistence.User; import org.cerberus.entities.persistence.User;
import org.cerberus.repositories.UserRepository; import org.cerberus.repositories.UserRepository;
import org.cerberus.services.UserService; import org.cerberus.services.UserService;
@@ -13,7 +14,6 @@ import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
@RestController @RestController
@RequestMapping("/api/users") @RequestMapping("/api/users")
@@ -31,11 +31,17 @@ public class UserController {
} }
@GetMapping("/disconnection") @GetMapping("/disconnection")
public void disconnection(final HttpServletRequest request, final HttpServletResponse response) { public void disconnection(HttpServletRequest request, HttpServletResponse response) {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication(); final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if(auth != null) { if(auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth); new SecurityContextLogoutHandler().logout(request, response, auth);
} }
response.setStatus(HttpServletResponse.SC_NO_CONTENT); 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);
}
} }

View File

@@ -50,7 +50,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
"/api/users/disconnection" "/api/users/disconnection"
).permitAll() ).permitAll()
.antMatchers(POST, .antMatchers(POST,
"/api/users/login" "/api/users/login",
"/api/users/signup"
).permitAll() ).permitAll()
.anyRequest().permitAll() .anyRequest().permitAll()
.and() .and()

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,20 +1,17 @@
package org.cerberus.entities.persistence; package org.cerberus.entities.persistence;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.Proxy; 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; import java.util.List;
@Entity @Entity
@Table(name="application") @Table(name="application")
@Proxy(lazy = false) @Proxy(lazy = false)
public class Application { public class Application extends AbstractEntity {
@Id
@Generated(GenerationTime.ALWAYS)
private String id;
@Column(nullable = false) @Column(nullable = false)
private String name; private String name;
@@ -27,14 +24,6 @@ public class Application {
@OneToMany(mappedBy = "application") @OneToMany(mappedBy = "application")
private List<ApplicationRole> administratorList; private List<ApplicationRole> administratorList;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() { public String getName() {
return name; return name;
} }

View File

@@ -3,8 +3,8 @@ package org.cerberus.entities.persistence;
import org.cerberus.core.constant.Role; import org.cerberus.core.constant.Role;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import java.util.UUID;
import static javax.persistence.FetchType.LAZY; import static javax.persistence.FetchType.LAZY;
@@ -14,23 +14,23 @@ public class ApplicationRole {
@Embeddable @Embeddable
public static class ApplicationRoleId implements Serializable { public static class ApplicationRoleId implements Serializable {
@Column(name = "user_id") @Column(name = "user_id")
private String userId; private UUID userId;
@Column(name = "application_id") @Column(name = "application_id")
private String applicationId; private UUID applicationId;
String getUserId() { UUID getUserId() {
return userId; return userId;
} }
void setUserId(String userId) { void setUserId(UUID userId) {
this.userId = userId; this.userId = userId;
} }
String getApplicationId() { UUID getApplicationId() {
return applicationId; return applicationId;
} }
void setApplicationId(String applicationId) { void setApplicationId(UUID applicationId) {
this.applicationId = applicationId; this.applicationId = applicationId;
} }
} }

View File

@@ -1,7 +1,5 @@
package org.cerberus.entities.persistence; package org.cerberus.entities.persistence;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.Proxy; import org.hibernate.annotations.Proxy;
import javax.persistence.*; import javax.persistence.*;
@@ -9,11 +7,7 @@ import javax.persistence.*;
@Entity @Entity
@Table(name="configuration_file") @Table(name="configuration_file")
@Proxy(lazy = false) @Proxy(lazy = false)
public class ConfigurationFile { public class ConfigurationFile extends AbstractEntity {
@Id
@Generated(GenerationTime.ALWAYS)
private String id;
@Column(nullable = false) @Column(nullable = false)
private String path; private String path;
@@ -21,14 +15,6 @@ public class ConfigurationFile {
@JoinColumn(name = "application_id") @JoinColumn(name = "application_id")
private Application application; private Application application;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPath() { public String getPath() {
return path; return path;
} }

View File

@@ -4,18 +4,17 @@ import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime; import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.Proxy; 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.time.LocalDate;
import java.util.List; import java.util.List;
@Entity @Entity
@Table(name="`user`") @Table(name="`user`")
@Proxy(lazy = false) @Proxy(lazy = false)
public class User { public class User extends AbstractEntity {
@Id
@Generated(GenerationTime.ALWAYS)
private String id;
@Column(nullable = false) @Column(nullable = false)
private String name; private String name;
@@ -35,14 +34,6 @@ public class User {
@OneToMany(mappedBy = "user") @OneToMany(mappedBy = "user")
private List<ApplicationRole> applicationRoleList; private List<ApplicationRole> applicationRoleList;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() { public String getName() {
return name; return name;
} }

View File

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

View File

@@ -15,4 +15,7 @@ public interface UserRepository extends JpaRepository<User, String> {
@Query("SELECT ar FROM ApplicationRole ar JOIN FETCH ar.application WHERE ar.user.email = :email") @Query("SELECT ar FROM ApplicationRole ar JOIN FETCH ar.application WHERE ar.user.email = :email")
List<ApplicationRole> getApplicationRolesByEmail(@Param("email") String email); List<ApplicationRole> getApplicationRolesByEmail(@Param("email") String email);
@Query(value = "SELECT EXISTS(SELECT id FROM \"user\" WHERE email = :email)", nativeQuery = true)
boolean isEmailAlreadyExists(@Param("email") String email);
} }

View File

@@ -3,9 +3,12 @@ package org.cerberus.services;
import org.cerberus.core.constant.Role; import org.cerberus.core.constant.Role;
import org.cerberus.core.config.security.CustomAuthenticationProvider; import org.cerberus.core.config.security.CustomAuthenticationProvider;
import org.cerberus.core.exceptions.BadRequestException; import org.cerberus.core.exceptions.BadRequestException;
import org.cerberus.entities.dto.SignUpDTO;
import org.cerberus.entities.persistence.ApplicationRole; import org.cerberus.entities.persistence.ApplicationRole;
import org.cerberus.entities.persistence.User; import org.cerberus.entities.persistence.User;
import org.cerberus.mappers.SignUpMapper;
import org.cerberus.repositories.UserRepository; import org.cerberus.repositories.UserRepository;
import org.cerberus.validators.SignUpValidator;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -18,10 +21,17 @@ import java.util.stream.Collectors;
@Service @Service
public class UserService { public class UserService {
private CustomAuthenticationProvider authenticationProvider; private CustomAuthenticationProvider authenticationProvider;
private SignUpMapper signUpMapper;
private SignUpValidator signUpValidator;
private UserRepository userRepository; private UserRepository userRepository;
public UserService(CustomAuthenticationProvider authenticationProvider, UserRepository userRepository) { public UserService(CustomAuthenticationProvider authenticationProvider,
SignUpMapper signUpMapper,
SignUpValidator signUpValidator,
UserRepository userRepository) {
this.authenticationProvider = authenticationProvider; this.authenticationProvider = authenticationProvider;
this.signUpMapper = signUpMapper;
this.signUpValidator = signUpValidator;
this.userRepository = userRepository; this.userRepository = userRepository;
} }
@@ -50,4 +60,14 @@ public class UserService {
.map(SimpleGrantedAuthority::new) .map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet()); .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));
}
} }

View File

@@ -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.");
}
}
}