From 8f761a4be43c7f041ce38e433fad5858ee453a32 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Wed, 5 May 2021 23:49:12 -0400 Subject: [PATCH] Employee -> User --- .../config/WebSecurityConfig.kt | 70 +++--- .../colorrecipesexplorer/model/Employee.kt | 193 ---------------- .../model/EmployeeGroup.kt | 141 ------------ .../colorrecipesexplorer/model/Recipe.kt | 6 +- .../model/account/Group.kt | 143 ++++++++++++ .../Permission.kt} | 14 +- .../model/account/User.kt | 195 ++++++++++++++++ .../repository/AccountRepository.kt | 14 +- .../rest/AccountControllers.kt | 82 +++---- .../service/AccountService.kt | 216 +++++++++--------- .../service/RecipeService.kt | 5 +- .../service/RecipeStepService.kt | 5 +- .../service/AccountsServiceTest.kt | 154 ++++++------- .../service/RecipeServiceTest.kt | 9 +- .../service/RecipeStepServiceTest.kt | 3 +- 15 files changed, 630 insertions(+), 620 deletions(-) delete mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt delete mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt rename src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/{EmployeePermission.kt => account/Permission.kt} (88%) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt index 0d4834c..1fb70ea 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/WebSecurityConfig.kt @@ -2,13 +2,13 @@ package dev.fyloz.colorrecipesexplorer.config import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.Employee -import dev.fyloz.colorrecipesexplorer.model.EmployeeLoginRequest -import dev.fyloz.colorrecipesexplorer.model.EmployeePermission -import dev.fyloz.colorrecipesexplorer.service.EmployeeService -import dev.fyloz.colorrecipesexplorer.service.EmployeeServiceImpl -import dev.fyloz.colorrecipesexplorer.service.EmployeeUserDetailsService -import dev.fyloz.colorrecipesexplorer.service.EmployeeUserDetailsServiceImpl +import dev.fyloz.colorrecipesexplorer.model.account.UserLoginRequest +import dev.fyloz.colorrecipesexplorer.model.account.Permission +import dev.fyloz.colorrecipesexplorer.model.account.User +import dev.fyloz.colorrecipesexplorer.service.UserService +import dev.fyloz.colorrecipesexplorer.service.UserServiceImpl +import dev.fyloz.colorrecipesexplorer.service.CreUserDetailsService +import dev.fyloz.colorrecipesexplorer.service.CreUserDetailsServiceImpl import io.jsonwebtoken.ExpiredJwtException import io.jsonwebtoken.Jwts import io.jsonwebtoken.SignatureAlgorithm @@ -31,7 +31,7 @@ import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.core.Authentication import org.springframework.security.core.AuthenticationException import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.User as SpringUser import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter @@ -52,11 +52,11 @@ import javax.servlet.http.HttpServletResponse @EnableGlobalMethodSecurity(prePostEnabled = true) @EnableConfigurationProperties(SecurityConfigurationProperties::class) class WebSecurityConfig( - val securityConfigurationProperties: SecurityConfigurationProperties, - @Lazy val userDetailsService: EmployeeUserDetailsServiceImpl, - @Lazy val employeeService: EmployeeServiceImpl, - val environment: Environment, - val logger: Logger + val securityConfigurationProperties: SecurityConfigurationProperties, + @Lazy val userDetailsService: CreUserDetailsServiceImpl, + @Lazy val userService: UserServiceImpl, + val environment: Environment, + val logger: Logger ) : WebSecurityConfigurerAdapter() { var debugMode = false @@ -95,15 +95,15 @@ class WebSecurityConfig( credentials: SecurityConfigurationProperties.SystemUserCredentials?, firstName: String, lastName: String, - permissions: List + permissions: List ) { Assert.notNull(credentials, "No root user has been defined.") credentials!! Assert.notNull(credentials.id, "The root user has no identifier defined.") Assert.notNull(credentials.password, "The root user has no password defined.") - if (!employeeService.existsById(credentials.id!!)) { - employeeService.save( - Employee( + if (!userService.existsById(credentials.id!!)) { + userService.save( + User( id = credentials.id!!, firstName = firstName, lastName = lastName, @@ -115,7 +115,7 @@ class WebSecurityConfig( } } - createUser(securityConfigurationProperties.root, "Root", "User", listOf(EmployeePermission.ADMIN)) + createUser(securityConfigurationProperties.root, "Root", "User", listOf(Permission.ADMIN)) debugMode = "debug" in environment.activeProfiles if (debugMode) logger.warn("Debug mode is enabled, security will be disabled!") } @@ -128,7 +128,7 @@ class WebSecurityConfig( .addFilter( JwtAuthenticationFilter( authenticationManager(), - employeeService, + userService, securityConfigurationProperties ) ) @@ -145,7 +145,7 @@ class WebSecurityConfig( http.authorizeRequests() .antMatchers("/api/login").permitAll() .antMatchers("/api/logout").authenticated() - .antMatchers("/api/employee/current").authenticated() + .antMatchers("/api/user/current").authenticated() .anyRequest().authenticated() } else { http @@ -171,9 +171,9 @@ const val defaultGroupCookieName = "Default-Group" val blacklistedJwtTokens = mutableListOf() class JwtAuthenticationFilter( - private val authManager: AuthenticationManager, - private val employeeService: EmployeeService, - private val securityConfigurationProperties: SecurityConfigurationProperties + private val authManager: AuthenticationManager, + private val userService: UserService, + private val securityConfigurationProperties: SecurityConfigurationProperties ) : UsernamePasswordAuthenticationFilter() { private var debugMode = false @@ -183,7 +183,7 @@ class JwtAuthenticationFilter( } override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication { - val loginRequest = jacksonObjectMapper().readValue(request.inputStream, EmployeeLoginRequest::class.java) + val loginRequest = jacksonObjectMapper().readValue(request.inputStream, UserLoginRequest::class.java) return authManager.authenticate(UsernamePasswordAuthenticationToken(loginRequest.id, loginRequest.password)) } @@ -197,12 +197,12 @@ class JwtAuthenticationFilter( val jwtDuration = securityConfigurationProperties.jwtDuration Assert.notNull(jwtSecret, "No JWT secret has been defined.") Assert.notNull(jwtDuration, "No JWT duration has been defined.") - val employeeId = (authResult.principal as User).username - employeeService.updateLastLoginTime(employeeId.toLong()) + val userId = (authResult.principal as SpringUser).username + userService.updateLastLoginTime(userId.toLong()) val expirationMs = System.currentTimeMillis() + jwtDuration!! val expirationDate = Date(expirationMs) val token = Jwts.builder() - .setSubject(employeeId) + .setSubject(userId) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, jwtSecret!!.toByteArray()) .compact() @@ -220,9 +220,9 @@ class JwtAuthenticationFilter( } class JwtAuthorizationFilter( - private val userDetailsService: EmployeeUserDetailsService, - private val securityConfigurationProperties: SecurityConfigurationProperties, - authenticationManager: AuthenticationManager + private val userDetailsService: CreUserDetailsService, + private val securityConfigurationProperties: SecurityConfigurationProperties, + authenticationManager: AuthenticationManager ) : BasicAuthenticationFilter(authenticationManager) { override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) { fun tryLoginFromBearer(): Boolean { @@ -259,20 +259,20 @@ class JwtAuthorizationFilter( val jwtSecret = securityConfigurationProperties.jwtSecret Assert.notNull(jwtSecret, "No JWT secret has been defined.") return try { - val employeeId = Jwts.parser() + val userId = Jwts.parser() .setSigningKey(jwtSecret!!.toByteArray()) .parseClaimsJws(token.replace("Bearer", "")) .body .subject - if (employeeId != null) getAuthenticationToken(employeeId) else null + if (userId != null) getAuthenticationToken(userId) else null } catch (_: ExpiredJwtException) { null } } - private fun getAuthenticationToken(employeeId: String): UsernamePasswordAuthenticationToken? = try { - val employeeDetails = userDetailsService.loadUserByEmployeeId(employeeId.toLong(), false) - UsernamePasswordAuthenticationToken(employeeDetails.username, null, employeeDetails.authorities) + private fun getAuthenticationToken(userId: String): UsernamePasswordAuthenticationToken? = try { + val userDetails = userDetailsService.loadUserById(userId.toLong(), false) + UsernamePasswordAuthenticationToken(userDetails.username, null, userDetails.authorities) } catch (_: NotFoundException) { null } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt deleted file mode 100644 index 1b98d41..0000000 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt +++ /dev/null @@ -1,193 +0,0 @@ -package dev.fyloz.colorrecipesexplorer.model - -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException -import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank -import org.hibernate.annotations.Fetch -import org.hibernate.annotations.FetchMode -import org.springframework.security.core.GrantedAuthority -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder -import org.springframework.security.crypto.password.PasswordEncoder -import java.time.LocalDateTime -import javax.persistence.* -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull -import javax.validation.constraints.Size - -private const val EMPLOYEE_ID_NULL_MESSAGE = "Un numéro d'employé est requis" -private const val EMPLOYEE_LAST_NAME_EMPTY_MESSAGE = "Un nom est requis" -private const val EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE = "Un prénom est requis" -private const val EMPLOYEE_PASSWORD_EMPTY_MESSAGE = "Un mot de passe est requis" -private const val EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit contenir au moins 8 caractères" - -@Entity -@Table(name = "employee") -data class Employee( - @Id - override val id: Long, - - @Column(name = "first_name") - val firstName: String = "", - - @Column(name = "last_name") - val lastName: String = "", - - val password: String = "", - - @Column(name = "default_group_user") - val isDefaultGroupUser: Boolean = false, - - @Column(name = "system_user") - val isSystemUser: Boolean = false, - - @ManyToOne - @JoinColumn(name = "group_id") - @Fetch(FetchMode.SELECT) - var group: EmployeeGroup? = null, - - @Enumerated(EnumType.STRING) - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "employee_permission", joinColumns = [JoinColumn(name = "employee_id")]) - @Column(name = "permission") - @Fetch(FetchMode.SUBSELECT) - val permissions: MutableSet = mutableSetOf(), - - @Column(name = "last_login_time") - var lastLoginTime: LocalDateTime? = null -) : Model { - val flatPermissions: Set - get() = permissions - .flatMap { it.flat() } - .filter { !it.deprecated } - .toMutableSet() - .apply { - if (group != null) this.addAll(group!!.flatPermissions) - } - - val authorities: Set - get() = flatPermissions.map { it.toAuthority() }.toMutableSet() -} - -/** DTO for creating employees. Allows a [password] a [groupId]. */ -open class EmployeeSaveDto( - @field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE) - val id: Long, - - @field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE) - val firstName: String, - - @field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE) - val lastName: String, - - @field:NotBlank(message = EMPLOYEE_PASSWORD_EMPTY_MESSAGE) - @field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE) - val password: String, - - val groupId: Long?, - - @Enumerated(EnumType.STRING) - val permissions: MutableSet = mutableSetOf() -) : EntityDto - -open class EmployeeUpdateDto( - @field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE) - val id: Long, - - @field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE) - val firstName: String?, - - @field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE) - val lastName: String?, - - val groupId: Long?, - - @Enumerated(EnumType.STRING) - val permissions: Set? -) : EntityDto - -data class EmployeeOutputDto( - override val id: Long, - val firstName: String, - val lastName: String, - val group: EmployeeGroup?, - val permissions: Set, - val explicitPermissions: Set, - val lastLoginTime: LocalDateTime? -) : Model - -data class EmployeeLoginRequest(val id: Long, val password: String) - -// ==== DSL ==== -fun employee( - passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), - id: Long = 0L, - firstName: String = "firstName", - lastName: String = "lastName", - password: String = passwordEncoder.encode("password"), - isDefaultGroupUser: Boolean = false, - isSystemUser: Boolean = false, - group: EmployeeGroup? = null, - permissions: MutableSet = mutableSetOf(), - lastLoginTime: LocalDateTime? = null, - op: Employee.() -> Unit = {} -) = Employee( - id, - firstName, - lastName, - password, - isDefaultGroupUser, - isSystemUser, - group, - permissions, - lastLoginTime -).apply(op) - -fun employeeSaveDto( - passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), - id: Long = 0L, - firstName: String = "firstName", - lastName: String = "lastName", - password: String = passwordEncoder.encode("password"), - groupId: Long? = null, - permissions: MutableSet = mutableSetOf(), - op: EmployeeSaveDto.() -> Unit = {} -) = EmployeeSaveDto(id, firstName, lastName, password, groupId, permissions).apply(op) - -fun employeeUpdateDto( - id: Long = 0L, - firstName: String = "firstName", - lastName: String = "lastName", - groupId: Long? = null, - permissions: MutableSet = mutableSetOf(), - op: EmployeeUpdateDto.() -> Unit = {} -) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op) - -// ==== Exceptions ==== -private const val EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE = "Employee not found" -private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee already exists" -private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employee" - -fun employeeIdNotFoundException(id: Long) = - NotFoundException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE, - "An employee with the id $id could not be found", - id - ) - -fun employeeIdAlreadyExistsException(id: Long) = - AlreadyExistsException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, - "An employee with the id $id already exists", - id - ) - -fun employeeFullNameAlreadyExistsException(firstName: String, lastName: String) = - AlreadyExistsException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, - "An employee with the name '$firstName $lastName' already exists", - "$firstName $lastName", - "fullName" - ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt deleted file mode 100644 index a88e0e4..0000000 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeeGroup.kt +++ /dev/null @@ -1,141 +0,0 @@ -package dev.fyloz.colorrecipesexplorer.model - -import com.fasterxml.jackson.annotation.JsonProperty -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException -import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.exception.RestException -import org.hibernate.annotations.Fetch -import org.hibernate.annotations.FetchMode -import org.springframework.http.HttpStatus -import javax.persistence.* -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull -import javax.validation.constraints.Size - -private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis" -private const val GROUP_NAME_NULL_MESSAGE = "Un nom est requis" -private const val GROUP_PERMISSIONS_EMPTY_MESSAGE = "Au moins une permission est requise" - -@Entity -@Table(name = "employee_group") -data class EmployeeGroup( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - override var id: Long? = null, - - @Column(unique = true) - override val name: String = "", - - @Enumerated(EnumType.STRING) - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")]) - @Column(name = "permission") - @Fetch(FetchMode.SUBSELECT) - val permissions: MutableSet = mutableSetOf(), -) : NamedModel { - val flatPermissions: Set - get() = this.permissions - .flatMap { it.flat() } - .filter { !it.deprecated } - .toSet() -} - -open class EmployeeGroupSaveDto( - @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) - @field:Size(min = 3) - val name: String, - - @field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE) - val permissions: MutableSet -) : EntityDto { - override fun toEntity(): EmployeeGroup = - EmployeeGroup(null, name, permissions) -} - -open class EmployeeGroupUpdateDto( - @field:NotNull(message = GROUP_ID_NULL_MESSAGE) - val id: Long, - - @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) - @field:Size(min = 3) - val name: String, - - @field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE) - val permissions: MutableSet -) : EntityDto { - override fun toEntity(): EmployeeGroup = - EmployeeGroup(id, name, permissions) -} - -data class EmployeeGroupOutputDto( - override val id: Long, - val name: String, - val permissions: Set, - val explicitPermissions: Set -): Model - -fun employeeGroup( - id: Long? = null, - name: String = "name", - permissions: MutableSet = mutableSetOf(), - op: EmployeeGroup.() -> Unit = {} -) = EmployeeGroup(id, name, permissions).apply(op) - -fun employeeGroupSaveDto( - name: String = "name", - permissions: MutableSet = mutableSetOf(), - op: EmployeeGroupSaveDto.() -> Unit = {} -) = EmployeeGroupSaveDto(name, permissions).apply(op) - -fun employeeGroupUpdateDto( - id: Long = 0L, - name: String = "name", - permissions: MutableSet = mutableSetOf(), - op: EmployeeGroupUpdateDto.() -> Unit = {} -) = EmployeeGroupUpdateDto(id, name, permissions).apply(op) - -// ==== Exceptions ==== -private const val EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE = "Employee group not found" -private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee group already exists" -private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employeegroup" - -class NoDefaultGroupException : RestException( - "nodefaultgroup", - "No default group", - HttpStatus.NOT_FOUND, - "No default group cookie is defined in the current request" -) - -fun employeeGroupIdNotFoundException(id: Long) = - NotFoundException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE, - "An employee group with the id $id could not be found", - id - ) - -fun employeeGroupNameNotFoundException(name: String) = - NotFoundException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE, - "An employee group with the name $name could not be found", - name, - "name" - ) - -fun employeeGroupIdAlreadyExistsException(id: Long) = - AlreadyExistsException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, - "An employee group with the id $id already exists", - id, - ) - -fun employeeGroupNameAlreadyExistsException(name: String) = - AlreadyExistsException( - EMPLOYEE_EXCEPTION_ERROR_CODE, - EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE, - "An employee group with the name $name already exists", - name, - "name" - ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt index 75c0287..cf0aad3 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt @@ -3,6 +3,8 @@ package dev.fyloz.colorrecipesexplorer.model import com.fasterxml.jackson.annotation.JsonIgnore import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException +import dev.fyloz.colorrecipesexplorer.model.account.Group +import dev.fyloz.colorrecipesexplorer.model.account.group import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES @@ -176,7 +178,7 @@ data class RecipeGroupInformation( @ManyToOne @JoinColumn(name = "group_id") - val group: EmployeeGroup, + val group: Group, var note: String?, @@ -264,7 +266,7 @@ fun recipeUpdateDto( fun recipeGroupInformation( id: Long? = null, - group: EmployeeGroup = employeeGroup(), + group: Group = group(), note: String? = null, steps: MutableSet? = mutableSetOf(), op: RecipeGroupInformation.() -> Unit = {} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt new file mode 100644 index 0000000..4bc229d --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Group.kt @@ -0,0 +1,143 @@ +package dev.fyloz.colorrecipesexplorer.model.account + +import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException +import dev.fyloz.colorrecipesexplorer.exception.NotFoundException +import dev.fyloz.colorrecipesexplorer.exception.RestException +import dev.fyloz.colorrecipesexplorer.model.EntityDto +import dev.fyloz.colorrecipesexplorer.model.Model +import dev.fyloz.colorrecipesexplorer.model.NamedModel +import org.hibernate.annotations.Fetch +import org.hibernate.annotations.FetchMode +import org.springframework.http.HttpStatus +import javax.persistence.* +import javax.validation.constraints.NotBlank +import javax.validation.constraints.NotNull +import javax.validation.constraints.Size + +private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis" +private const val GROUP_NAME_NULL_MESSAGE = "Un nom est requis" +private const val GROUP_PERMISSIONS_EMPTY_MESSAGE = "Au moins une permission est requise" + +@Entity +@Table(name = "user_group") +data class Group( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + override var id: Long? = null, + + @Column(unique = true) + override val name: String = "", + + @Enumerated(EnumType.STRING) + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")]) + @Column(name = "permission") + @Fetch(FetchMode.SUBSELECT) + val permissions: MutableSet = mutableSetOf(), +) : NamedModel { + val flatPermissions: Set + get() = this.permissions + .flatMap { it.flat() } + .filter { !it.deprecated } + .toSet() +} + +open class GroupSaveDto( + @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) + @field:Size(min = 3) + val name: String, + + @field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE) + val permissions: MutableSet +) : EntityDto { + override fun toEntity(): Group = + Group(null, name, permissions) +} + +open class GroupUpdateDto( + @field:NotNull(message = GROUP_ID_NULL_MESSAGE) + val id: Long, + + @field:NotBlank(message = GROUP_NAME_NULL_MESSAGE) + @field:Size(min = 3) + val name: String, + + @field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE) + val permissions: MutableSet +) : EntityDto { + override fun toEntity(): Group = + Group(id, name, permissions) +} + +data class GroupOutputDto( + override val id: Long, + val name: String, + val permissions: Set, + val explicitPermissions: Set +): Model + +fun group( + id: Long? = null, + name: String = "name", + permissions: MutableSet = mutableSetOf(), + op: Group.() -> Unit = {} +) = Group(id, name, permissions).apply(op) + +fun groupSaveDto( + name: String = "name", + permissions: MutableSet = mutableSetOf(), + op: GroupSaveDto.() -> Unit = {} +) = GroupSaveDto(name, permissions).apply(op) + +fun groupUpdateDto( + id: Long = 0L, + name: String = "name", + permissions: MutableSet = mutableSetOf(), + op: GroupUpdateDto.() -> Unit = {} +) = GroupUpdateDto(id, name, permissions).apply(op) + +// ==== Exceptions ==== +private const val GROUP_NOT_FOUND_EXCEPTION_TITLE = "Group not found" +private const val GROUP_ALREADY_EXISTS_EXCEPTION_TITLE = "Group already exists" +private const val GROUP_EXCEPTION_ERROR_CODE = "group" + +class NoDefaultGroupException : RestException( + "nodefaultgroup", + "No default group", + HttpStatus.NOT_FOUND, + "No default group cookie is defined in the current request" +) + +fun groupIdNotFoundException(id: Long) = + NotFoundException( + GROUP_EXCEPTION_ERROR_CODE, + GROUP_NOT_FOUND_EXCEPTION_TITLE, + "A group with the id $id could not be found", + id + ) + +fun groupNameNotFoundException(name: String) = + NotFoundException( + GROUP_EXCEPTION_ERROR_CODE, + GROUP_NOT_FOUND_EXCEPTION_TITLE, + "A group with the name $name could not be found", + name, + "name" + ) + +fun groupIdAlreadyExistsException(id: Long) = + AlreadyExistsException( + GROUP_EXCEPTION_ERROR_CODE, + GROUP_ALREADY_EXISTS_EXCEPTION_TITLE, + "A group with the id $id already exists", + id, + ) + +fun groupNameAlreadyExistsException(name: String) = + AlreadyExistsException( + GROUP_EXCEPTION_ERROR_CODE, + GROUP_ALREADY_EXISTS_EXCEPTION_TITLE, + "A group with the name $name already exists", + name, + "name" + ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeePermission.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Permission.kt similarity index 88% rename from src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeePermission.kt rename to src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Permission.kt index 5124243..333f8fb 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/EmployeePermission.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/Permission.kt @@ -1,10 +1,10 @@ -package dev.fyloz.colorrecipesexplorer.model +package dev.fyloz.colorrecipesexplorer.model.account import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority -enum class EmployeePermission( - val impliedPermissions: List = listOf(), +enum class Permission( + val impliedPermissions: List = listOf(), val deprecated: Boolean = false ) { READ_FILE, @@ -80,12 +80,12 @@ enum class EmployeePermission( SET_BROWSER_DEFAULT_GROUP(listOf(VIEW_USERS), true), ; - operator fun contains(permission: EmployeePermission): Boolean { + operator fun contains(permission: Permission): Boolean { return permission == this || impliedPermissions.any { permission in it } } } -fun EmployeePermission.flat(): Iterable { +fun Permission.flat(): Iterable { return mutableSetOf(this).apply { impliedPermissions.forEach { addAll(it.flat()) @@ -93,7 +93,7 @@ fun EmployeePermission.flat(): Iterable { } } -/** Converts the given [EmployeePermission] to a [GrantedAuthority]. */ -fun EmployeePermission.toAuthority(): GrantedAuthority { +/** Converts the given [Permission] to a [GrantedAuthority]. */ +fun Permission.toAuthority(): GrantedAuthority { return SimpleGrantedAuthority(name) } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt new file mode 100644 index 0000000..c6e2b58 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/account/User.kt @@ -0,0 +1,195 @@ +package dev.fyloz.colorrecipesexplorer.model.account + +import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException +import dev.fyloz.colorrecipesexplorer.exception.NotFoundException +import dev.fyloz.colorrecipesexplorer.model.EntityDto +import dev.fyloz.colorrecipesexplorer.model.Model +import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank +import org.hibernate.annotations.Fetch +import org.hibernate.annotations.FetchMode +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder +import java.time.LocalDateTime +import javax.persistence.* +import javax.validation.constraints.NotBlank +import javax.validation.constraints.NotNull +import javax.validation.constraints.Size + +private const val USER_ID_NULL_MESSAGE = "Un numéro d'utilisateur est requis" +private const val USER_LAST_NAME_EMPTY_MESSAGE = "Un nom est requis" +private const val USER_FIRST_NAME_EMPTY_MESSAGE = "Un prénom est requis" +private const val USER_PASSWORD_EMPTY_MESSAGE = "Un mot de passe est requis" +private const val USER_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit contenir au moins 8 caractères" + +@Entity +@Table(name = "user") +data class User( + @Id + override val id: Long, + + @Column(name = "first_name") + val firstName: String = "", + + @Column(name = "last_name") + val lastName: String = "", + + val password: String = "", + + @Column(name = "default_group_user") + val isDefaultGroupUser: Boolean = false, + + @Column(name = "system_user") + val isSystemUser: Boolean = false, + + @ManyToOne + @JoinColumn(name = "group_id") + @Fetch(FetchMode.SELECT) + var group: Group? = null, + + @Enumerated(EnumType.STRING) + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "user_permission", joinColumns = [JoinColumn(name = "user_id")]) + @Column(name = "permission") + @Fetch(FetchMode.SUBSELECT) + val permissions: MutableSet = mutableSetOf(), + + @Column(name = "last_login_time") + var lastLoginTime: LocalDateTime? = null +) : Model { + val flatPermissions: Set + get() = permissions + .flatMap { it.flat() } + .filter { !it.deprecated } + .toMutableSet() + .apply { + if (group != null) this.addAll(group!!.flatPermissions) + } + + val authorities: Set + get() = flatPermissions.map { it.toAuthority() }.toMutableSet() +} + +/** DTO for creating users. Allows a [password] a [groupId]. */ +open class UserSaveDto( + @field:NotNull(message = USER_ID_NULL_MESSAGE) + val id: Long, + + @field:NotBlank(message = USER_FIRST_NAME_EMPTY_MESSAGE) + val firstName: String, + + @field:NotBlank(message = USER_LAST_NAME_EMPTY_MESSAGE) + val lastName: String, + + @field:NotBlank(message = USER_PASSWORD_EMPTY_MESSAGE) + @field:Size(min = 8, message = USER_PASSWORD_TOO_SHORT_MESSAGE) + val password: String, + + val groupId: Long?, + + @Enumerated(EnumType.STRING) + val permissions: MutableSet = mutableSetOf() +) : EntityDto + +open class UserUpdateDto( + @field:NotNull(message = USER_ID_NULL_MESSAGE) + val id: Long, + + @field:NullOrNotBlank(message = USER_FIRST_NAME_EMPTY_MESSAGE) + val firstName: String?, + + @field:NullOrNotBlank(message = USER_LAST_NAME_EMPTY_MESSAGE) + val lastName: String?, + + val groupId: Long?, + + @Enumerated(EnumType.STRING) + val permissions: Set? +) : EntityDto + +data class UserOutputDto( + override val id: Long, + val firstName: String, + val lastName: String, + val group: Group?, + val permissions: Set, + val explicitPermissions: Set, + val lastLoginTime: LocalDateTime? +) : Model + +data class UserLoginRequest(val id: Long, val password: String) + +// ==== DSL ==== +fun user( + passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), + id: Long = 0L, + firstName: String = "firstName", + lastName: String = "lastName", + password: String = passwordEncoder.encode("password"), + isDefaultGroupUser: Boolean = false, + isSystemUser: Boolean = false, + group: Group? = null, + permissions: MutableSet = mutableSetOf(), + lastLoginTime: LocalDateTime? = null, + op: User.() -> Unit = {} +) = User( + id, + firstName, + lastName, + password, + isDefaultGroupUser, + isSystemUser, + group, + permissions, + lastLoginTime +).apply(op) + +fun userSaveDto( + passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(), + id: Long = 0L, + firstName: String = "firstName", + lastName: String = "lastName", + password: String = passwordEncoder.encode("password"), + groupId: Long? = null, + permissions: MutableSet = mutableSetOf(), + op: UserSaveDto.() -> Unit = {} +) = UserSaveDto(id, firstName, lastName, password, groupId, permissions).apply(op) + +fun userUpdateDto( + id: Long = 0L, + firstName: String = "firstName", + lastName: String = "lastName", + groupId: Long? = null, + permissions: MutableSet = mutableSetOf(), + op: UserUpdateDto.() -> Unit = {} +) = UserUpdateDto(id, firstName, lastName, groupId, permissions).apply(op) + +// ==== Exceptions ==== +private const val USER_NOT_FOUND_EXCEPTION_TITLE = "User not found" +private const val USER_ALREADY_EXISTS_EXCEPTION_TITLE = "User already exists" +private const val USER_EXCEPTION_ERROR_CODE = "user" + +fun userIdNotFoundException(id: Long) = + NotFoundException( + USER_EXCEPTION_ERROR_CODE, + USER_NOT_FOUND_EXCEPTION_TITLE, + "An user with the id $id could not be found", + id + ) + +fun userIdAlreadyExistsException(id: Long) = + AlreadyExistsException( + USER_EXCEPTION_ERROR_CODE, + USER_ALREADY_EXISTS_EXCEPTION_TITLE, + "An user with the id $id already exists", + id + ) + +fun userFullNameAlreadyExistsException(firstName: String, lastName: String) = + AlreadyExistsException( + USER_EXCEPTION_ERROR_CODE, + USER_ALREADY_EXISTS_EXCEPTION_TITLE, + "An user with the name '$firstName $lastName' already exists", + "$firstName $lastName", + "fullName" + ) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/AccountRepository.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/AccountRepository.kt index b1f6099..d2548ea 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/AccountRepository.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/AccountRepository.kt @@ -1,20 +1,20 @@ package dev.fyloz.colorrecipesexplorer.repository -import dev.fyloz.colorrecipesexplorer.model.Employee -import dev.fyloz.colorrecipesexplorer.model.EmployeeGroup +import dev.fyloz.colorrecipesexplorer.model.account.Group +import dev.fyloz.colorrecipesexplorer.model.account.User import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface EmployeeRepository : JpaRepository { +interface UserRepository : JpaRepository { fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean - fun findByFirstNameAndLastName(firstName: String, lastName: String): Employee? + fun findByFirstNameAndLastName(firstName: String, lastName: String): User? - fun findAllByGroup(group: EmployeeGroup): Collection + fun findAllByGroup(group: Group): Collection - fun findByIsDefaultGroupUserIsTrueAndGroupIs(group: EmployeeGroup): Employee + fun findByIsDefaultGroupUserIsTrueAndGroupIs(group: Group): User } @Repository -interface EmployeeGroupRepository : NamedJpaRepository +interface GroupRepository : NamedJpaRepository diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt index ea9efde..62f6d03 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/AccountControllers.kt @@ -3,9 +3,9 @@ package dev.fyloz.colorrecipesexplorer.rest import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditUsers import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveUsers import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewUsers -import dev.fyloz.colorrecipesexplorer.model.* -import dev.fyloz.colorrecipesexplorer.service.EmployeeGroupService -import dev.fyloz.colorrecipesexplorer.service.EmployeeService +import dev.fyloz.colorrecipesexplorer.model.account.* +import dev.fyloz.colorrecipesexplorer.service.UserService +import dev.fyloz.colorrecipesexplorer.service.GroupService import org.springframework.http.MediaType import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* @@ -14,29 +14,29 @@ import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import javax.validation.Valid -private const val EMPLOYEE_CONTROLLER_PATH = "api/employee" -private const val EMPLOYEE_GROUP_CONTROLLER_PATH = "api/employee/group" +private const val USER_CONTROLLER_PATH = "api/user" +private const val GROUP_CONTROLLER_PATH = "api/user/group" @RestController -@RequestMapping(EMPLOYEE_CONTROLLER_PATH) -class EmployeeController(private val employeeService: EmployeeService) { +@RequestMapping(USER_CONTROLLER_PATH) +class UserController(private val userService: UserService) { @GetMapping @PreAuthorizeViewUsers fun getAll() = - ok(employeeService.getAllForOutput()) + ok(userService.getAllForOutput()) @GetMapping("{id}") @PreAuthorizeViewUsers fun getById(@PathVariable id: Long) = - ok(employeeService.getByIdForOutput(id)) + ok(userService.getByIdForOutput(id)) @GetMapping("current") - fun getCurrent(loggedInEmployee: Principal?) = - if (loggedInEmployee != null) + fun getCurrent(loggedInUser: Principal?) = + if (loggedInUser != null) ok( - with(employeeService) { + with(userService) { getById( - loggedInEmployee.name.toLong(), + loggedInUser.name.toLong(), ignoreDefaultGroupUsers = false, ignoreSystemUsers = false ).toOutput() @@ -47,56 +47,56 @@ class EmployeeController(private val employeeService: EmployeeService) { @PostMapping @PreAuthorizeEditUsers - fun save(@Valid @RequestBody employee: EmployeeSaveDto) = - created(EMPLOYEE_CONTROLLER_PATH) { - with(employeeService) { - save(employee).toOutput() + fun save(@Valid @RequestBody user: UserSaveDto) = + created(USER_CONTROLLER_PATH) { + with(userService) { + save(user).toOutput() } } @PutMapping @PreAuthorizeEditUsers - fun update(@Valid @RequestBody employee: EmployeeUpdateDto) = + fun update(@Valid @RequestBody user: UserUpdateDto) = noContent { - employeeService.update(employee) + userService.update(user) } @PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE]) @PreAuthorizeEditUsers fun updatePassword(@PathVariable id: Long, @RequestBody password: String) = noContent { - employeeService.updatePassword(id, password) + userService.updatePassword(id, password) } - @PutMapping("{employeeId}/permissions/{permission}") + @PutMapping("{userId}/permissions/{permission}") @PreAuthorizeEditUsers fun addPermission( - @PathVariable employeeId: Long, - @PathVariable permission: EmployeePermission + @PathVariable userId: Long, + @PathVariable permission: Permission ) = noContent { - employeeService.addPermission(employeeId, permission) + userService.addPermission(userId, permission) } - @DeleteMapping("{employeeId}/permissions/{permission}") + @DeleteMapping("{userId}/permissions/{permission}") @PreAuthorizeEditUsers fun removePermission( - @PathVariable employeeId: Long, - @PathVariable permission: EmployeePermission + @PathVariable userId: Long, + @PathVariable permission: Permission ) = noContent { - employeeService.removePermission(employeeId, permission) + userService.removePermission(userId, permission) } @DeleteMapping("{id}") @PreAuthorizeRemoveUsers fun deleteById(@PathVariable id: Long) = - employeeService.deleteById(id) + userService.deleteById(id) } @RestController -@RequestMapping(EMPLOYEE_GROUP_CONTROLLER_PATH) +@RequestMapping(GROUP_CONTROLLER_PATH) class GroupsController( - private val groupService: EmployeeGroupService, - private val employeeService: EmployeeService + private val groupService: GroupService, + private val userService: UserService ) { @GetMapping @PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')") @@ -108,11 +108,11 @@ class GroupsController( fun getById(@PathVariable id: Long) = ok(groupService.getByIdForOutput(id)) - @GetMapping("{id}/employees") + @GetMapping("{id}/users") @PreAuthorizeViewUsers - fun getEmployeesForGroup(@PathVariable id: Long) = - ok(with(employeeService) { - groupService.getEmployeesForGroup(id) + fun getUsersForGroup(@PathVariable id: Long) = + ok(with(userService) { + groupService.getUsersForGroup(id) .map { it.toOutput() } }) @@ -132,8 +132,8 @@ class GroupsController( @PostMapping @PreAuthorizeEditUsers - fun save(@Valid @RequestBody group: EmployeeGroupSaveDto) = - created(EMPLOYEE_GROUP_CONTROLLER_PATH) { + fun save(@Valid @RequestBody group: GroupSaveDto) = + created(GROUP_CONTROLLER_PATH) { with(groupService) { save(group).toOutput() } @@ -141,7 +141,7 @@ class GroupsController( @PutMapping @PreAuthorizeEditUsers - fun update(@Valid @RequestBody group: EmployeeGroupUpdateDto) = + fun update(@Valid @RequestBody group: GroupUpdateDto) = noContent { groupService.update(group) } @@ -156,10 +156,10 @@ class GroupsController( @RestController @RequestMapping("api") -class LogoutController(private val employeeService: EmployeeService) { +class LogoutController(private val userService: UserService) { @GetMapping("logout") fun logout(request: HttpServletRequest) = ok { - employeeService.logout(request) + userService.logout(request) } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt index 8f08ed4..5d6bd11 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountService.kt @@ -3,12 +3,11 @@ package dev.fyloz.colorrecipesexplorer.service import dev.fyloz.colorrecipesexplorer.config.blacklistedJwtTokens import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.* +import dev.fyloz.colorrecipesexplorer.model.account.* import dev.fyloz.colorrecipesexplorer.model.validation.or -import dev.fyloz.colorrecipesexplorer.repository.EmployeeGroupRepository -import dev.fyloz.colorrecipesexplorer.repository.EmployeeRepository +import dev.fyloz.colorrecipesexplorer.repository.UserRepository +import dev.fyloz.colorrecipesexplorer.repository.GroupRepository import org.springframework.context.annotation.Lazy -import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException @@ -19,73 +18,74 @@ import java.time.LocalDateTime import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import javax.transaction.Transactional +import org.springframework.security.core.userdetails.User as SpringUser -interface EmployeeService : - ExternalModelService { - /** Check if an [Employee] with the given [firstName] and [lastName] exists. */ +interface UserService : + ExternalModelService { + /** Check if an [User] with the given [firstName] and [lastName] exists. */ fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean - /** Gets the employee with the given [id]. */ - fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee + /** Gets the user with the given [id]. */ + fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User - /** Gets all employees which have the given [group]. */ - fun getByGroup(group: EmployeeGroup): Collection + /** Gets all users which have the given [group]. */ + fun getByGroup(group: Group): Collection /** Gets the default user of the given [group]. */ - fun getDefaultGroupEmployee(group: EmployeeGroup): Employee + fun getDefaultGroupUser(group: Group): User - /** Save a default group employee for the given [group]. */ - fun saveDefaultGroupEmployee(group: EmployeeGroup) + /** Save a default group user for the given [group]. */ + fun saveDefaultGroupUser(group: Group) /** Updates de given [entity]. **/ - fun update(entity: Employee, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee + fun update(entity: User, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User - /** Updates the last login time of the employee with the given [employeeId]. */ - fun updateLastLoginTime(employeeId: Long, time: LocalDateTime = LocalDateTime.now()): Employee + /** Updates the last login time of the user with the given [userId]. */ + fun updateLastLoginTime(userId: Long, time: LocalDateTime = LocalDateTime.now()): User - /** Updates the password of the employee with the given [id]. */ - fun updatePassword(id: Long, password: String): Employee + /** Updates the password of the user with the given [id]. */ + fun updatePassword(id: Long, password: String): User - /** Adds the given [permission] to the employee with the given [employeeId]. */ - fun addPermission(employeeId: Long, permission: EmployeePermission): Employee + /** Adds the given [permission] to the user with the given [userId]. */ + fun addPermission(userId: Long, permission: Permission): User - /** Removes the given [permission] from the employee with the given [employeeId]. */ - fun removePermission(employeeId: Long, permission: EmployeePermission): Employee + /** Removes the given [permission] from the user with the given [userId]. */ + fun removePermission(userId: Long, permission: Permission): User /** Logout an user. Add the authorization token of the given [request] to the blacklisted tokens. */ fun logout(request: HttpServletRequest) } -interface EmployeeGroupService : - ExternalNamedModelService { - /** Gets all the employees of the group with the given [id]. */ - fun getEmployeesForGroup(id: Long): Collection +interface GroupService : + ExternalNamedModelService { + /** Gets all the users of the group with the given [id]. */ + fun getUsersForGroup(id: Long): Collection /** Gets the default group from a cookie in the given HTTP [request]. */ - fun getRequestDefaultGroup(request: HttpServletRequest): EmployeeGroup + fun getRequestDefaultGroup(request: HttpServletRequest): Group /** Sets the default group cookie for the given HTTP [response]. */ fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse) } -interface EmployeeUserDetailsService : UserDetailsService { - /** Loads an [User] for the given [employeeId]. */ - fun loadUserByEmployeeId(employeeId: Long, ignoreDefaultGroupUsers: Boolean = false): UserDetails +interface CreUserDetailsService : UserDetailsService { + /** Loads an [User] for the given [id]. */ + fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean = false): UserDetails } @Service -class EmployeeServiceImpl( - employeeRepository: EmployeeRepository, - @Lazy val groupService: EmployeeGroupService, +class UserServiceImpl( + userRepository: UserRepository, + @Lazy val groupService: GroupService, @Lazy val passwordEncoder: PasswordEncoder, -) : AbstractExternalModelService( - employeeRepository +) : AbstractExternalModelService( + userRepository ), - EmployeeService { - override fun idNotFoundException(id: Long) = employeeIdNotFoundException(id) - override fun idAlreadyExistsException(id: Long) = employeeIdAlreadyExistsException(id) + UserService { + override fun idNotFoundException(id: Long) = userIdNotFoundException(id) + override fun idAlreadyExistsException(id: Long) = userIdAlreadyExistsException(id) - override fun Employee.toOutput() = EmployeeOutputDto( + override fun User.toOutput() = UserOutputDto( this.id, this.firstName, this.lastName, @@ -98,29 +98,29 @@ class EmployeeServiceImpl( override fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean = repository.existsByFirstNameAndLastName(firstName, lastName) - override fun getAll(): Collection = + override fun getAll(): Collection = super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser } - override fun getById(id: Long): Employee = + override fun getById(id: Long): User = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) - override fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee = + override fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User = super.getById(id).apply { if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser) throw idNotFoundException(id) } - override fun getByGroup(group: EmployeeGroup): Collection = + override fun getByGroup(group: Group): Collection = repository.findAllByGroup(group).filter { !it.isSystemUser && !it.isDefaultGroupUser } - override fun getDefaultGroupEmployee(group: EmployeeGroup): Employee = + override fun getDefaultGroupUser(group: Group): User = repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group) - override fun save(entity: EmployeeSaveDto): Employee = + override fun save(entity: UserSaveDto): User = save(with(entity) { - Employee( + User( id, firstName, lastName, @@ -132,20 +132,20 @@ class EmployeeServiceImpl( ) }) - override fun save(entity: Employee): Employee { + override fun save(entity: User): User { if (existsById(entity.id)) - throw employeeIdAlreadyExistsException(entity.id) + throw userIdAlreadyExistsException(entity.id) if (existsByFirstNameAndLastName(entity.firstName, entity.lastName)) - throw employeeFullNameAlreadyExistsException(entity.firstName, entity.lastName) + throw userFullNameAlreadyExistsException(entity.firstName, entity.lastName) return super.save(entity) } - override fun saveDefaultGroupEmployee(group: EmployeeGroup) { + override fun saveDefaultGroupUser(group: Group) { save( - employee( + user( id = 1000000L + group.id!!, firstName = group.name, - lastName = "EmployeeModel", + lastName = "User", password = passwordEncoder.encode(group.name), group = group, isDefaultGroupUser = true @@ -153,49 +153,49 @@ class EmployeeServiceImpl( ) } - override fun updateLastLoginTime(employeeId: Long, time: LocalDateTime): Employee { - val employee = getById(employeeId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false) - employee.lastLoginTime = time + override fun updateLastLoginTime(userId: Long, time: LocalDateTime): User { + val user = getById(userId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false) + user.lastLoginTime = time return update( - employee, + user, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false ) } - override fun update(entity: EmployeeUpdateDto): Employee { - val persistedEmployee by lazy { getById(entity.id) } + override fun update(entity: UserUpdateDto): User { + val persistedUser by lazy { getById(entity.id) } return update(with(entity) { - Employee( + User( id = id, - firstName = firstName or persistedEmployee.firstName, - lastName = lastName or persistedEmployee.lastName, - password = persistedEmployee.password, + firstName = firstName or persistedUser.firstName, + lastName = lastName or persistedUser.lastName, + password = persistedUser.password, isDefaultGroupUser = false, isSystemUser = false, - group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group, - permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions, - lastLoginTime = persistedEmployee.lastLoginTime + group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedUser.group, + permissions = permissions?.toMutableSet() ?: persistedUser.permissions, + lastLoginTime = persistedUser.lastLoginTime ) }) } - override fun update(entity: Employee): Employee = + override fun update(entity: User): User = update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) - override fun update(entity: Employee, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee { + override fun update(entity: User, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): User { with(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)) { if (this != null && id != entity.id) - throw employeeFullNameAlreadyExistsException(entity.firstName, entity.lastName) + throw userFullNameAlreadyExistsException(entity.firstName, entity.lastName) } - return super.update(entity) + return super.update(entity) } - override fun updatePassword(id: Long, password: String): Employee { - val persistedEmployee = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) - return super.update(with(persistedEmployee) { - Employee( + override fun updatePassword(id: Long, password: String): User { + val persistedUser = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true) + return super.update(with(persistedUser) { + User( id, firstName, lastName, @@ -209,11 +209,11 @@ class EmployeeServiceImpl( }) } - override fun addPermission(employeeId: Long, permission: EmployeePermission): Employee = - super.update(getById(employeeId).apply { permissions += permission }) + override fun addPermission(userId: Long, permission: Permission): User = + super.update(getById(userId).apply { permissions += permission }) - override fun removePermission(employeeId: Long, permission: EmployeePermission): Employee = - super.update(getById(employeeId).apply { permissions -= permission }) + override fun removePermission(userId: Long, permission: Permission): User = + super.update(getById(userId).apply { permissions -= permission }) override fun logout(request: HttpServletRequest) { val authorizationCookie = WebUtils.getCookie(request, "Authorization") @@ -229,19 +229,19 @@ class EmployeeServiceImpl( const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans @Service -class EmployeeGroupServiceImpl( - private val employeeService: EmployeeService, - employeeGroupRepository: EmployeeGroupRepository -) : AbstractExternalNamedModelService( - employeeGroupRepository +class GroupServiceImpl( + private val userService: UserService, + groupRepository: GroupRepository +) : AbstractExternalNamedModelService( + groupRepository ), - EmployeeGroupService { - override fun idNotFoundException(id: Long) = employeeGroupIdNotFoundException(id) - override fun idAlreadyExistsException(id: Long) = employeeGroupIdAlreadyExistsException(id) - override fun nameNotFoundException(name: String) = employeeGroupNameNotFoundException(name) - override fun nameAlreadyExistsException(name: String) = employeeGroupNameAlreadyExistsException(name) + GroupService { + override fun idNotFoundException(id: Long) = groupIdNotFoundException(id) + override fun idAlreadyExistsException(id: Long) = groupIdAlreadyExistsException(id) + override fun nameNotFoundException(name: String) = groupNameNotFoundException(name) + override fun nameAlreadyExistsException(name: String) = groupNameAlreadyExistsException(name) - override fun EmployeeGroup.toOutput() = EmployeeGroupOutputDto( + override fun Group.toOutput() = GroupOutputDto( this.id!!, this.name, this.permissions, @@ -249,20 +249,20 @@ class EmployeeGroupServiceImpl( ) override fun existsByName(name: String): Boolean = repository.existsByName(name) - override fun getEmployeesForGroup(id: Long): Collection = - employeeService.getByGroup(getById(id)) + override fun getUsersForGroup(id: Long): Collection = + userService.getByGroup(getById(id)) @Transactional - override fun save(entity: EmployeeGroup): EmployeeGroup { + override fun save(entity: Group): Group { return super.save(entity).apply { - employeeService.saveDefaultGroupEmployee(this) + userService.saveDefaultGroupUser(this) } } - override fun update(entity: EmployeeGroupUpdateDto): EmployeeGroup { + override fun update(entity: GroupUpdateDto): Group { val persistedGroup by lazy { getById(entity.id) } return update(with(entity) { - EmployeeGroup( + Group( entity.id, if (name.isNotBlank()) entity.name else persistedGroup.name, if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions @@ -271,15 +271,15 @@ class EmployeeGroupServiceImpl( } @Transactional - override fun delete(entity: EmployeeGroup) { - employeeService.delete(employeeService.getDefaultGroupEmployee(entity)) + override fun delete(entity: Group) { + userService.delete(userService.getDefaultGroupUser(entity)) super.delete(entity) } - override fun getRequestDefaultGroup(request: HttpServletRequest): EmployeeGroup { + override fun getRequestDefaultGroup(request: HttpServletRequest): Group { val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName) ?: throw NoDefaultGroupException() - val defaultGroupUser = employeeService.getById( + val defaultGroupUser = userService.getById( defaultGroupCookie.value.toLong(), ignoreDefaultGroupUsers = false, ignoreSystemUsers = true @@ -289,7 +289,7 @@ class EmployeeGroupServiceImpl( override fun setResponseDefaultGroup(groupId: Long, response: HttpServletResponse) { val group = getById(groupId) - val defaultGroupUser = employeeService.getDefaultGroupEmployee(group) + val defaultGroupUser = userService.getDefaultGroupUser(group) response.addHeader( "Set-Cookie", "$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=${defaultGroupCookieMaxAge}; Path=/api; HttpOnly; Secure; SameSite=strict" @@ -298,13 +298,13 @@ class EmployeeGroupServiceImpl( } @Service -class EmployeeUserDetailsServiceImpl( - private val employeeService: EmployeeService +class CreUserDetailsServiceImpl( + private val userService: UserService ) : - EmployeeUserDetailsService { + CreUserDetailsService { override fun loadUserByUsername(username: String): UserDetails { try { - return loadUserByEmployeeId(username.toLong(), true) + return loadUserById(username.toLong(), true) } catch (ex: NotFoundException) { throw UsernameNotFoundException(username) } catch (ex: NotFoundException) { @@ -312,12 +312,12 @@ class EmployeeUserDetailsServiceImpl( } } - override fun loadUserByEmployeeId(employeeId: Long, ignoreDefaultGroupUsers: Boolean): UserDetails { - val employee = employeeService.getById( - employeeId, + override fun loadUserById(id: Long, ignoreDefaultGroupUsers: Boolean): UserDetails { + val user = userService.getById( + id, ignoreDefaultGroupUsers = ignoreDefaultGroupUsers, ignoreSystemUsers = false ) - return User(employee.id.toString(), employee.password, employee.authorities) + return SpringUser(user.id.toString(), user.password, user.authorities) } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt index b170e81..7f0a1cc 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt @@ -1,6 +1,7 @@ package dev.fyloz.colorrecipesexplorer.service import dev.fyloz.colorrecipesexplorer.model.* +import dev.fyloz.colorrecipesexplorer.model.account.Group import dev.fyloz.colorrecipesexplorer.model.validation.or import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository import dev.fyloz.colorrecipesexplorer.service.files.FileService @@ -38,7 +39,7 @@ class RecipeServiceImpl( val companyService: CompanyService, val mixService: MixService, val recipeStepService: RecipeStepService, - @Lazy val groupService: EmployeeGroupService, + @Lazy val groupService: GroupService, val recipeImageService: RecipeImageService ) : AbstractExternalModelService( @@ -157,7 +158,7 @@ class RecipeServiceImpl( if (publicDataDto.notes != null) { val recipe = getById(publicDataDto.recipeId) - fun noteForGroup(group: EmployeeGroup) = + fun noteForGroup(group: Group) = publicDataDto.notes.firstOrNull { it.groupId == group.id }?.content // Notes diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt index a72fc76..2475ed3 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt @@ -2,6 +2,7 @@ package dev.fyloz.colorrecipesexplorer.service import dev.fyloz.colorrecipesexplorer.exception.RestException import dev.fyloz.colorrecipesexplorer.model.* +import dev.fyloz.colorrecipesexplorer.model.account.Group import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository import dev.fyloz.colorrecipesexplorer.utils.findDuplicated import dev.fyloz.colorrecipesexplorer.utils.hasGaps @@ -81,8 +82,8 @@ class InvalidStepsPositionsException( ) class InvalidGroupStepsPositionsException( - val group: EmployeeGroup, - val exception: InvalidStepsPositionsException + val group: Group, + val exception: InvalidStepsPositionsException ) : RestException( "invalid-groupinformation-recipestep-position", "Invalid steps positions", diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt index a844d91..ba55ec5 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AccountsServiceTest.kt @@ -4,15 +4,14 @@ import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.* -import dev.fyloz.colorrecipesexplorer.repository.EmployeeGroupRepository -import dev.fyloz.colorrecipesexplorer.repository.EmployeeRepository +import dev.fyloz.colorrecipesexplorer.model.account.* +import dev.fyloz.colorrecipesexplorer.repository.UserRepository +import dev.fyloz.colorrecipesexplorer.repository.GroupRepository import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.springframework.mock.web.MockHttpServletResponse -import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import java.util.* @@ -22,24 +21,25 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue +import org.springframework.security.core.userdetails.User as SpringUser -class EmployeeServiceTest : - AbstractExternalModelServiceTest() { +class UserServiceTest : + AbstractExternalModelServiceTest() { private val passwordEncoder = BCryptPasswordEncoder() - override val entity: Employee = employee(passwordEncoder, id = 0L) - override val anotherEntity: Employee = employee(passwordEncoder, id = 1L) - private val entityDefaultGroupUser = employee(passwordEncoder, id = 2L, isDefaultGroupUser = true) - private val entitySystemUser = employee(passwordEncoder, id = 3L, isSystemUser = true) - private val group = employeeGroup(id = 0L) - override val entitySaveDto: EmployeeSaveDto = spy(employeeSaveDto(passwordEncoder, id = 0L)) - override val entityUpdateDto: EmployeeUpdateDto = spy(employeeUpdateDto(id = 0L)) + override val entity: User = user(passwordEncoder, id = 0L) + override val anotherEntity: User = user(passwordEncoder, id = 1L) + private val entityDefaultGroupUser = user(passwordEncoder, id = 2L, isDefaultGroupUser = true) + private val entitySystemUser = user(passwordEncoder, id = 3L, isSystemUser = true) + private val group = group(id = 0L) + override val entitySaveDto: UserSaveDto = spy(userSaveDto(passwordEncoder, id = 0L)) + override val entityUpdateDto: UserUpdateDto = spy(userUpdateDto(id = 0L)) - override val repository: EmployeeRepository = mock() - private val employeeGroupService: EmployeeGroupService = mock() - override val service: EmployeeService = spy(EmployeeServiceImpl(repository, employeeGroupService, passwordEncoder)) + override val repository: UserRepository = mock() + private val groupService: GroupService = mock() + override val service: UserService = spy(UserServiceImpl(repository, groupService, passwordEncoder)) - private val entitySaveDtoEmployee = Employee( + private val entitySaveDtoUser = User( entitySaveDto.id, entitySaveDto.firstName, entitySaveDto.lastName, @@ -52,14 +52,14 @@ class EmployeeServiceTest : @AfterEach override fun afterEach() { - reset(employeeGroupService) + reset(groupService) super.afterEach() } // existsByFirstNameAndLastName() @Test - fun `existsByFirstNameAndLastName() returns true when an employee with the given first name and last name exists`() { + fun `existsByFirstNameAndLastName() returns true when an user with the given first name and last name exists`() { whenever(repository.existsByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn(true) val found = service.existsByFirstNameAndLastName(entity.firstName, entity.lastName) @@ -68,7 +68,7 @@ class EmployeeServiceTest : } @Test - fun `existsByFirstNameAndLastName() returns false when no employee with the given first name and last name exists`() { + fun `existsByFirstNameAndLastName() returns false when no user with the given first name and last name exists`() { whenever(repository.existsByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn(false) val found = service.existsByFirstNameAndLastName(entity.firstName, entity.lastName) @@ -79,7 +79,7 @@ class EmployeeServiceTest : // getById() @Test - fun `getById() throws NotFoundException when the corresponding employee is a default group user`() { + fun `getById() throws NotFoundException when the corresponding user is a default group user`() { whenever(repository.findById(entityDefaultGroupUser.id)).doReturn(Optional.of(entityDefaultGroupUser)) assertThrows { @@ -92,7 +92,7 @@ class EmployeeServiceTest : } @Test - fun `getById() throws NotFoundException when the corresponding employee is a system user`() { + fun `getById() throws NotFoundException when the corresponding user is a system user`() { whenever(repository.findById(entitySystemUser.id)).doReturn(Optional.of(entitySystemUser)) assertThrows { @@ -107,7 +107,7 @@ class EmployeeServiceTest : // getByGroup() @Test - fun `getByGroup() returns all the employees with the given group from the repository`() { + fun `getByGroup() returns all the users with the given group from the repository`() { whenever(repository.findAllByGroup(group)).doReturn(entityList) val found = service.getByGroup(group) @@ -117,7 +117,7 @@ class EmployeeServiceTest : } @Test - fun `getByGroup() returns an empty list when there is no employee with the given group in the repository`() { + fun `getByGroup() returns an empty list when there is no user with the given group in the repository`() { whenever(repository.findAllByGroup(group)).doReturn(listOf()) val found = service.getByGroup(group) @@ -128,10 +128,10 @@ class EmployeeServiceTest : // getDefaultGroupUser() @Test - fun `getDefaultGroupUser() returns the default employee of the given group from the repository`() { + fun `getDefaultGroupUser() returns the default user of the given group from the repository`() { whenever(repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)).doReturn(entityDefaultGroupUser) - val found = service.getDefaultGroupEmployee(group) + val found = service.getDefaultGroupUser(group) assertEquals(entityDefaultGroupUser, found) } @@ -166,13 +166,13 @@ class EmployeeServiceTest : } @Test - fun `save(dto) calls and returns save() with the created employee`() { - doReturn(entitySaveDtoEmployee).whenever(service).save(any()) + fun `save(dto) calls and returns save() with the created user`() { + doReturn(entitySaveDtoUser).whenever(service).save(any()) val found = service.save(entitySaveDto) - verify(service).save(argThat { this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName }) - assertEquals(entitySaveDtoEmployee, found) + verify(service).save(argThat { this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName }) + assertEquals(entitySaveDtoUser, found) } // update() @@ -182,7 +182,7 @@ class EmployeeServiceTest : withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() }) @Test - fun `update() throws AlreadyExistsException when a different employee with the given first name and last name exists`() { + fun `update() throws AlreadyExistsException when a different user with the given first name and last name exists`() { whenever(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn( entityDefaultGroupUser ) @@ -198,47 +198,47 @@ class EmployeeServiceTest : } } -class EmployeeGroupServiceTest : - AbstractExternalNamedModelServiceTest() { - private val employeeService: EmployeeService = mock() - override val repository: EmployeeGroupRepository = mock() - override val service: EmployeeGroupServiceImpl = spy(EmployeeGroupServiceImpl(employeeService, repository)) +class GroupServiceTest : + AbstractExternalNamedModelServiceTest() { + private val userService: UserService = mock() + override val repository: GroupRepository = mock() + override val service: GroupServiceImpl = spy(GroupServiceImpl(userService, repository)) - override val entity: EmployeeGroup = employeeGroup(id = 0L, name = "group") - override val anotherEntity: EmployeeGroup = employeeGroup(id = 1L, name = "another group") - override val entitySaveDto: EmployeeGroupSaveDto = spy(employeeGroupSaveDto(name = "group")) - override val entityUpdateDto: EmployeeGroupUpdateDto = spy(employeeGroupUpdateDto(id = 0L, name = "group")) - override val entityWithEntityName: EmployeeGroup = employeeGroup(id = 2L, name = entity.name) + override val entity: Group = group(id = 0L, name = "group") + override val anotherEntity: Group = group(id = 1L, name = "another group") + override val entitySaveDto: GroupSaveDto = spy(groupSaveDto(name = "group")) + override val entityUpdateDto: GroupUpdateDto = spy(groupUpdateDto(id = 0L, name = "group")) + override val entityWithEntityName: Group = group(id = 2L, name = entity.name) - private val groupEmployeeId = 1000000L + entity.id!! - private val groupEmployee = employee(BCryptPasswordEncoder(), id = groupEmployeeId, group = entity) + private val groupUserId = 1000000L + entity.id!! + private val groupUser = user(BCryptPasswordEncoder(), id = groupUserId, group = entity) @BeforeEach override fun afterEach() { - reset(employeeService) + reset(userService) super.afterEach() } - // getEmployeesForGroup() + // getUsersForGroup() @Test - fun `getEmployeesForGroup() returns all employees in the given group`() { - val group = employeeGroup(id = 1L) + fun `getUsersForGroup() returns all users in the given group`() { + val group = group(id = 1L) doReturn(group).whenever(service).getById(group.id!!) - whenever(employeeService.getByGroup(group)).doReturn(listOf(groupEmployee)) + whenever(userService.getByGroup(group)).doReturn(listOf(groupUser)) - val found = service.getEmployeesForGroup(group.id!!) + val found = service.getUsersForGroup(group.id!!) - assertTrue(found.contains(groupEmployee)) + assertTrue(found.contains(groupUser)) assertTrue(found.size == 1) } @Test - fun `getEmployeesForGroup() returns empty collection when the given group contains any employee`() { + fun `getUsersForGroup() returns empty collection when the given group contains any user`() { doReturn(entity).whenever(service).getById(entity.id!!) - val found = service.getEmployeesForGroup(entity.id!!) + val found = service.getUsersForGroup(entity.id!!) assertTrue(found.isEmpty()) } @@ -247,11 +247,11 @@ class EmployeeGroupServiceTest : @Test fun `getRequestDefaultGroup() returns the group contained in the cookie of the HTTP request`() { - val cookies: Array = arrayOf(Cookie(defaultGroupCookieName, groupEmployeeId.toString())) + val cookies: Array = arrayOf(Cookie(defaultGroupCookieName, groupUserId.toString())) val request: HttpServletRequest = mock() whenever(request.cookies).doReturn(cookies) - whenever(employeeService.getById(eq(groupEmployeeId), any(), any())).doReturn(groupEmployee) + whenever(userService.getById(eq(groupUserId), any(), any())).doReturn(groupUser) val found = service.getRequestDefaultGroup(request) @@ -273,7 +273,7 @@ class EmployeeGroupServiceTest : fun `setResponseDefaultGroup() the default group cookie has been added to the given HTTP response with the given group id`() { val response = MockHttpServletResponse() - whenever(employeeService.getDefaultGroupEmployee(entity)).doReturn(groupEmployee) + whenever(userService.getDefaultGroupUser(entity)).doReturn(groupUser) doReturn(entity).whenever(service).getById(entity.id!!) service.setResponseDefaultGroup(entity.id!!, response) @@ -281,7 +281,7 @@ class EmployeeGroupServiceTest : assertNotNull(found) assertEquals(defaultGroupCookieName, found.name) - assertEquals(groupEmployeeId.toString(), found.value) + assertEquals(groupUserId.toString(), found.value) assertEquals(defaultGroupCookieMaxAge, found.maxAge) assertTrue(found.isHttpOnly) assertTrue(found.secure) @@ -301,48 +301,48 @@ class EmployeeGroupServiceTest : withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() }) } -class EmployeeUserDetailsServiceTest { - private val employeeService: EmployeeService = mock() - private val service = spy(EmployeeUserDetailsServiceImpl(employeeService)) +class UserUserDetailsServiceTest { + private val userService: UserService = mock() + private val service = spy(CreUserDetailsServiceImpl(userService)) - private val employee = employee(id = 0L) + private val user = user(id = 0L) @BeforeEach fun beforeEach() { - reset(employeeService, service) + reset(userService, service) } // loadUserByUsername() @Test - fun `loadUserByUsername() calls loadUserByEmployeeId() with the given username as an id`() { - whenever(employeeService.getById(eq(employee.id), any(), any())).doReturn(employee) - doReturn(User(employee.id.toString(), employee.password, listOf())).whenever(service) - .loadUserByEmployeeId(employee.id) + fun `loadUserByUsername() calls loadUserByUserId() with the given username as an id`() { + whenever(userService.getById(eq(user.id), any(), any())).doReturn(user) + doReturn(SpringUser(user.id.toString(), user.password, listOf())).whenever(service) + .loadUserById(user.id) - service.loadUserByUsername(employee.id.toString()) + service.loadUserByUsername(user.id.toString()) - verify(service).loadUserByEmployeeId(eq(employee.id), any()) + verify(service).loadUserById(eq(user.id), any()) } @Test - fun `loadUserByUsername() throws UsernameNotFoundException when no employee with the given id exists`() { - whenever(employeeService.getById(eq(employee.id), any(), any())).doThrow( - employeeIdNotFoundException(employee.id) + fun `loadUserByUsername() throws UsernameNotFoundException when no user with the given id exists`() { + whenever(userService.getById(eq(user.id), any(), any())).doThrow( + userIdNotFoundException(user.id) ) - assertThrows { service.loadUserByUsername(employee.id.toString()) } + assertThrows { service.loadUserByUsername(user.id.toString()) } } - // loadUserByEmployeeId + // loadUserByUserId @Test - fun `loadUserByEmployeeId() returns an User corresponding to the employee with the given id`() { - whenever(employeeService.getById(eq(employee.id), any(), any())).doReturn(employee) + fun `loadUserByUserId() returns an User corresponding to the user with the given id`() { + whenever(userService.getById(eq(user.id), any(), any())).doReturn(user) - val found = service.loadUserByEmployeeId(employee.id) + val found = service.loadUserById(user.id) - assertEquals(employee.id, found.username.toLong()) - assertEquals(employee.password, found.password) + assertEquals(user.id, found.username.toLong()) + assertEquals(user.password, found.password) } } diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt index 859f4d9..dd3793c 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt @@ -3,6 +3,7 @@ package dev.fyloz.colorrecipesexplorer.service import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.model.* +import dev.fyloz.colorrecipesexplorer.model.account.group import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository import dev.fyloz.colorrecipesexplorer.service.files.FileService import io.mockk.* @@ -21,7 +22,7 @@ class RecipeServiceTest : override val repository: RecipeRepository = mock() private val companyService: CompanyService = mock() private val mixService: MixService = mock() - private val groupService: EmployeeGroupService = mock() + private val groupService: GroupService = mock() private val recipeStepService: RecipeStepService = mock() override val service: RecipeService = spy(RecipeServiceImpl(repository, companyService, mixService, recipeStepService, groupService, mock())) @@ -129,9 +130,9 @@ class RecipeServiceTest : fun `updatePublicData() updates the notes of a recipe groups information according to the RecipePublicDataDto`() { val recipe = recipe( id = 0L, groupsInformation = setOf( - recipeGroupInformation(id = 0L, group = employeeGroup(id = 1L), note = "Old note"), - recipeGroupInformation(id = 1L, group = employeeGroup(id = 2L), note = "Another note"), - recipeGroupInformation(id = 2L, group = employeeGroup(id = 3L), note = "Up to date note") + recipeGroupInformation(id = 0L, group = group(id = 1L), note = "Old note"), + recipeGroupInformation(id = 1L, group = group(id = 2L), note = "Another note"), + recipeGroupInformation(id = 2L, group = group(id = 3L), note = "Up to date note") ) ) val notes = setOf( diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt index 48f476f..4fe2e5a 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt @@ -2,6 +2,7 @@ package dev.fyloz.colorrecipesexplorer.service import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.model.* +import dev.fyloz.colorrecipesexplorer.model.account.group import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -80,7 +81,7 @@ class RecipeStepServiceTest : private fun withGroupInformation(steps: MutableSet? = null, test: RecipeGroupInformation.() -> Unit) { recipeGroupInformation( - group = employeeGroup(id = 0L), + group = group(id = 0L), steps = steps ?: mutableSetOf( recipeStep(id = 0L, position = 1), recipeStep(id = 1L, position = 2),