Skip to content

Commit

Permalink
Refactor user managing
Browse files Browse the repository at this point in the history
  • Loading branch information
BilledTrain380 committed Oct 7, 2018
1 parent 4bd7c54 commit f66bdaa
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,10 @@ package ch.schulealtendorf.sporttagpsa.business.setup

import ch.schulealtendorf.sporttagpsa.business.user.USER_ADMIN
import ch.schulealtendorf.sporttagpsa.business.user.UserManager
import ch.schulealtendorf.sporttagpsa.business.user.UserPassword
import ch.schulealtendorf.sporttagpsa.entity.DEFAULT_SETUP
import ch.schulealtendorf.sporttagpsa.repository.SetupRepository
import org.springframework.stereotype.Component
import java.util.Random

import java.util.*


/**
Expand Down Expand Up @@ -77,7 +75,7 @@ class StatefulSetupManager(

// set admin password
val user = userManager.getOne(USER_ADMIN).get()
userManager.update(UserPassword(user.userId, setup.adminPassword))
userManager.changePassword(user, setup.adminPassword)

// mark setup as initialized and set new JWT secret
val setupEntity = setupRepository.findById(DEFAULT_SETUP).get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class PSAUserDetailService(
true,
true,
userEntity.authorities.map {
SimpleGrantedAuthority("ROLE_${it.role}") as GrantedAuthority
SimpleGrantedAuthority("ROLE_${it.role}") as GrantedAuthority // TODO: remove role prefix
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@

package ch.schulealtendorf.sporttagpsa.business.user

import ch.schulealtendorf.sporttagpsa.model.User
import ch.schulealtendorf.sporttagpsa.business.user.validation.InvalidPasswordException
import java.util.*

/**
Expand All @@ -48,29 +50,31 @@ import java.util.*
interface UserManager {

/**
* Creates the given {@code user}.
* The {@code FreshUser#password} field will be encrypted.
*
* @param user the user to create
*
* @throws UserAlreadyExistsException if the user exists already
*/
fun create(user: FreshUser)

/**
* Updates the password for the given {@code user}.
* The {@code UserPassword#password} field will be encrypted.
*
* @param user the user password to update
* Saves the given {@code user}. If the user does not exist yet, it will be created.
*
* The {@code password} property will
* not be considered at all.
*
* To update the password use the
* {@link UserManager#update} method.
*
* @param user the user to save
*
* @return the saved user
* @throws InvalidPasswordException if the password does not match the validation requirements
*/
fun update(user: UserPassword)
fun save(user: User): User

/**
* Updates the given {@code user}.
*
* @param user the user to update
* Changes the password for the given {@code user}.
* The password will be encrypted before its being saved.
*
* @param user the user to change its password
* @param password the password to use
*
* @throws UserNotFoundException if the given {@code user} does not exist
*/
fun update(user: User)
fun changePassword(user: User, password: String)

/**
* @return all users
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@

package ch.schulealtendorf.sporttagpsa.business.user

import ch.schulealtendorf.sporttagpsa.business.user.validation.InvalidPasswordException
import ch.schulealtendorf.sporttagpsa.business.user.validation.PasswordValidator
import ch.schulealtendorf.sporttagpsa.entity.AuthorityEntity
import ch.schulealtendorf.sporttagpsa.entity.UserEntity
import ch.schulealtendorf.sporttagpsa.model.User
import ch.schulealtendorf.sporttagpsa.repository.UserRepository
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.stereotype.Component
Expand All @@ -51,79 +54,53 @@ import java.util.*
*/
@Component
class UserManagerImpl(
private val userRepository: UserRepository
private val userRepository: UserRepository,
private val passwordValidator: PasswordValidator
): UserManager {

/**
* Creates the given {@code user}.
* The {@code FreshUser#password} field will be encrypted with {@link BCryptPasswordEncoder}.
*
* @param user the user to create
*
* @throws UserAlreadyExistsException if the user exists already
*/
override fun create(user: FreshUser) {

if(userRepository.findByUsername(user.username).isPresent) {
throw UserAlreadyExistsException("User exists already: username=${user.username}")
override fun save(user: User): User {

val userEntity = userRepository.findById(user.id)
.orElseGet {
user.password.validate()
UserEntity(password = user.password.encode())
}

userEntity.apply {
username = user.username
enabled = user.enabled
authorities = user.authorities.map { AuthorityEntity(it) }
}

val userEntity = UserEntity(null, user.username, user.password.encode(), user.enabled, listOf(
AuthorityEntity("USER")
))

userRepository.save(userEntity)
}

/**
* Updates the password for the given {@code user}.
* The {@code UserPassword#password} field will be encrypted.
*
* @param user the user password to update
*/
override fun update(user: UserPassword) {

val userEntity = userRepository.findById(user.userId).get()

userEntity.password = user.password.encode()

userRepository.save(userEntity)
return userRepository.save(userEntity).toModel()
}

/**
* Updates the given {@code user}.
*
* @param user the user to update
*/
override fun update(user: User) {

val userEntity: UserEntity = userRepository.findById(user.userId).get()

userEntity.username = user.username
userEntity.enabled = user.enabled

override fun changePassword(user: User, password: String) {

val userEntity = userRepository.findById(user.id)
.orElseThrow { UserNotFoundException("The user could not be found: user=$user") }

password.validate()
userEntity.password = password.encode()

userRepository.save(userEntity)
}

/**
* @return all users
*/
override fun getAll(): List<User> = userRepository.findAll().map { it.toModel() }

override fun getOne(userId: Int): Optional<User> = userRepository.findById(userId).map { it.toModel() }

override fun getOne(username: String): Optional<User> = userRepository.findByUsername(username).map { it.toModel() }

/**
* Deletes the user matching the given {@code userId}.
*
* @param userId id of the user to delete
*/
override fun delete(userId: Int) {
userRepository.deleteById(userId)
}
override fun delete(userId: Int) = userRepository.deleteById(userId)

private fun String.encode(): String = BCryptPasswordEncoder(4).encode(this)

private fun UserEntity.toModel() = User(id!!, username, enabled)
private fun UserEntity.toModel() = User(id!!, username, authorities.map { it.role }, enabled)

private fun String.validate() {
val validationResult = passwordValidator.validate(this)
if (validationResult.isValid.not())
throw InvalidPasswordException(validationResult.messages.joinToString(", "))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,3 @@
package ch.schulealtendorf.sporttagpsa.business.user

const val USER_ADMIN: String = "admin"

data class FreshUser(
val username: String,
val password: String,
val enabled: Boolean
)

data class UserPassword(
val userId: Int,
val password: String
)

data class User(
val userId: Int,
val username: String,
val enabled: Boolean
)

data class UserLogin(
val username: String,
val password: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2018 by Nicolas Märchy
*
* This file is part of Sporttag PSA.
*
* Sporttag PSA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Sporttag PSA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Sporttag PSA. If not, see <http://www.gnu.org/licenses/>.
*
* Diese Datei ist Teil von Sporttag PSA.
*
* Sporttag PSA ist Freie Software: Sie können es unter den Bedingungen
* der GNU General Public License, wie von der Free Software Foundation,
* Version 3 der Lizenz oder (nach Ihrer Wahl) jeder späteren
* veröffentlichten Version, weiterverbreiten und/oder modifizieren.
*
* Sporttag PSA wird in der Hoffnung, dass es nützlich sein wird, aber
* OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
* Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
* Siehe die GNU General Public License für weitere Details.
*
* Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
* Programm erhalten haben. Wenn nicht, siehe <http://www.gnu.org/licenses/>.
*
*
*/

package ch.schulealtendorf.sporttagpsa.business.user

/**
* Exception indicating that a specific user could not be found.
*
* @author nmaerchy <billedtrain380@gmail.com>
* @since 2.0.0
*/
class UserNotFoundException: Exception {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(message, cause, enableSuppression, writableStackTrace)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2018 by Nicolas Märchy
*
* This file is part of Sporttag PSA.
*
* Sporttag PSA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Sporttag PSA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Sporttag PSA. If not, see <http://www.gnu.org/licenses/>.
*
* Diese Datei ist Teil von Sporttag PSA.
*
* Sporttag PSA ist Freie Software: Sie können es unter den Bedingungen
* der GNU General Public License, wie von der Free Software Foundation,
* Version 3 der Lizenz oder (nach Ihrer Wahl) jeder späteren
* veröffentlichten Version, weiterverbreiten und/oder modifizieren.
*
* Sporttag PSA wird in der Hoffnung, dass es nützlich sein wird, aber
* OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
* Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
* Siehe die GNU General Public License für weitere Details.
*
* Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
* Programm erhalten haben. Wenn nicht, siehe <http://www.gnu.org/licenses/>.
*
*
*/

package ch.schulealtendorf.sporttagpsa.business.user.validation

/**
* Exception indicating that a password does not match
* the required validation rules.
*
* @author nmaerchy <billedtrain380@gmail.com>
* @since 2.0.0
*/
class InvalidPasswordException: Exception {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(message, cause, enableSuppression, writableStackTrace)
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ data class UserEntity(
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int? = 0,
var id: Int? = null,

@NotNull
@Size(min = 1, max = 50)
Expand Down
6 changes: 5 additions & 1 deletion src/main/kotlin/ch/schulealtendorf/sporttagpsa/model/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@ package ch.schulealtendorf.sporttagpsa.model

/**
* Data class representing a user.
* The {@code password} property should only be set
* to create a new user.
*
* @author nmaerchy <billedtrain380@gmail.com>
* @since 2.0.0
*/
data class User @JvmOverloads constructor(
val id: Int,
val username: String,
val roles: List<String> = listOf()
val authorities: List<String>,
val enabled: Boolean = true,
val password: String = "protected"
)
Loading

0 comments on commit f66bdaa

Please sign in to comment.