diff --git a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/controllers/UserRegistrationController.kt b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/controllers/UserRegistrationController.kt index 5c334f7..e5b69d7 100644 --- a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/controllers/UserRegistrationController.kt +++ b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/controllers/UserRegistrationController.kt @@ -42,9 +42,9 @@ class UserRegistrationController( fun registerPost( @Valid @ModelAttribute("form") user: UserRegistrationForm, result: BindingResult - ): String { + ): ModelAndView { if (!registrationEnabled) { - return "registration/registration_disabled" + return ModelAndView("registration/registration_disabled", HttpStatus.FORBIDDEN) } if (!user.userName.isBlank() && userRepository.findByUserName(user.userName) != null) { @@ -56,14 +56,14 @@ class UserRegistrationController( } if (result.hasErrors()) { - return "registration/create_account" + return ModelAndView("registration/create_account", HttpStatus.BAD_REQUEST) } val newUser = User(userName = user.userName, email = user.email, firstName = user.firstName, lastName = user.lastName, enabled = true) userRepository.save(newUser) eventPublisher.publishEvent(OnEmailVerificationRequestedEvent(newUser)) - return "registration/verify_email" + return ModelAndView("registration/verify_email") } @GetMapping(value = ["/register/activate/"], params = ["token"]) diff --git a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/controllers/admin/AdminController.kt b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/controllers/admin/AdminController.kt new file mode 100644 index 0000000..a109e2d --- /dev/null +++ b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/controllers/admin/AdminController.kt @@ -0,0 +1,17 @@ +package pl.edu.uj.ii.ksi.mordor.controllers.admin + +import org.springframework.security.access.annotation.Secured +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.servlet.View +import org.springframework.web.servlet.view.RedirectView +import pl.edu.uj.ii.ksi.mordor.persistence.entities.Permission + +@Controller +class AdminController { + @Secured(Permission.ACCESS_ADMIN_PANEL_STR) + @GetMapping("/admin/") + fun adminPage(): View { + return RedirectView("/admin/users/") + } +} diff --git a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/controllers/admin/UsersController.kt b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/controllers/admin/UsersController.kt new file mode 100644 index 0000000..3211f1f --- /dev/null +++ b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/controllers/admin/UsersController.kt @@ -0,0 +1,122 @@ +package pl.edu.uj.ii.ksi.mordor.controllers.admin + +import javax.validation.Valid +import org.springframework.data.domain.PageRequest +import org.springframework.http.HttpStatus +import org.springframework.security.access.annotation.Secured +import org.springframework.stereotype.Controller +import org.springframework.validation.BindingResult +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.ModelAttribute +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.servlet.ModelAndView +import org.springframework.web.servlet.View +import org.springframework.web.servlet.view.RedirectView +import pl.edu.uj.ii.ksi.mordor.exceptions.BadRequestException +import pl.edu.uj.ii.ksi.mordor.forms.UserForm +import pl.edu.uj.ii.ksi.mordor.persistence.entities.Permission +import pl.edu.uj.ii.ksi.mordor.persistence.entities.Role +import pl.edu.uj.ii.ksi.mordor.persistence.entities.User +import pl.edu.uj.ii.ksi.mordor.persistence.repositories.UserRepository +import pl.edu.uj.ii.ksi.mordor.services.UserManagerService + +@Controller +class UsersController(private val userRepository: UserRepository, private val userManagerService: UserManagerService) { + companion object { + private const val usersPerPage = 100 + } + + @Secured(Permission.ACCESS_ADMIN_PANEL_STR) + @GetMapping("/admin/users/") + fun userList(): View { + return RedirectView("/admin/users/0/") + } + + @Secured(Permission.ACCESS_ADMIN_PANEL_STR) + @GetMapping("/admin/users/{num}/") + fun userList(@PathVariable("num") pageNumber: Int): ModelAndView { + val users = userRepository.findAll(PageRequest.of(pageNumber, usersPerPage)) + return ModelAndView("admin/user_list", "users", users) + } + + @Secured(Permission.MANAGE_USERS_STR) + @GetMapping("/admin/user/{id}/") + fun userProfile(@PathVariable("id") userId: Long): ModelAndView { + val user = userRepository.findById(userId) + if (user.isPresent) { + val u = user.get() + val userForm = UserForm(u.id!!, u.userName, u.email, "", "", u.firstName, u.lastName, u.enabled, u.role) + return ModelAndView("admin/user_profile", "user", userForm) + } else { + throw BadRequestException("No user for id: $userId") + } + } + + @Secured(Permission.MANAGE_USERS_STR) + @PostMapping("/admin/user/{id}/") + fun userProfileEdit( + @PathVariable("id") userId: Long, + @Valid @ModelAttribute("user") form: UserForm, + result: BindingResult + ): ModelAndView { + if (form.id != userId) { + throw BadRequestException("User id mismatch") + } + if (form.password != form.password2) { + result.rejectValue("password2", "password2.notMatch", "passwords do not match") + } + if (result.hasErrors()) { + return ModelAndView("admin/user_profile", HttpStatus.BAD_REQUEST) + } + val user = userRepository.findById(form.id).orElseThrow { BadRequestException("unknown user") } + user.email = form.email + user.firstName = form.firstName + user.lastName = form.lastName + user.enabled = form.enabled + user.role = form.role + if (!form.password.isNullOrBlank()) { + user.password = userManagerService.hashPassword(form.password) + } + userRepository.save(user) + return ModelAndView(RedirectView("/admin/users/")) + } + + @Secured(Permission.MANAGE_USERS_STR) + @PostMapping("/admin/user/{id}/delete/") + fun userProfileDelete( + @PathVariable("id") userId: Long + ): ModelAndView { + val user = userRepository.findById(userId).orElseThrow { BadRequestException("unknown user") } + userRepository.delete(user) + return ModelAndView(RedirectView("/admin/users/")) + } + + @Secured(Permission.MANAGE_USERS_STR) + @GetMapping("/admin/user/create/") + fun userProfileCreate(): ModelAndView { + return ModelAndView("admin/user_create", "user", UserForm(null, "", "", "", "", "", "", true, Role.USER)) + } + + @Secured(Permission.MANAGE_USERS_STR) + @PostMapping("/admin/user/create/") + fun userProfileCreatePost(@Valid @ModelAttribute("user") form: UserForm, result: BindingResult): ModelAndView { + if (!form.userName.isBlank() && userRepository.findByUserName(form.userName) != null) { + result.rejectValue("userName", "username.exists", "username unavailable") + } + + if (form.email != null && !form.email.isBlank() && userRepository.findByEmail(form.email) != null) { + result.rejectValue("email", "email.exists", "email already in use") + } + if (form.password != form.password2) { + result.rejectValue("password2", "password2.notMatch", "passwords do not match") + } + if (result.hasErrors()) { + return ModelAndView("admin/user_create", HttpStatus.BAD_REQUEST) + } + val user = User(null, form.userName, form.password, form.email, + form.firstName, form.lastName, form.enabled, form.role) + userRepository.save(user) + return ModelAndView(RedirectView("/admin/users/")) + } +} diff --git a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/forms/UserForm.kt b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/forms/UserForm.kt new file mode 100644 index 0000000..6380aff --- /dev/null +++ b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/forms/UserForm.kt @@ -0,0 +1,23 @@ +package pl.edu.uj.ii.ksi.mordor.forms + +import pl.edu.uj.ii.ksi.mordor.persistence.entities.Role + +data class UserForm( + val id: Long?, + + val userName: String, + + val email: String?, + + val password: String?, + + val password2: String?, + + val firstName: String?, + + val lastName: String?, + + val enabled: Boolean, + + val role: Role +) diff --git a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/persistence/entities/Permission.kt b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/persistence/entities/Permission.kt index 90e3b4d..5b617e6 100644 --- a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/persistence/entities/Permission.kt +++ b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/persistence/entities/Permission.kt @@ -7,6 +7,7 @@ enum class Permission : GrantedAuthority { ROLE_UPLOAD, ROLE_WRITE, ROLE_LIST_HIDDEN_FILES, + ROLE_ACCESS_ADMIN_PANEL, ROLE_MANAGE_USERS; companion object { @@ -14,6 +15,7 @@ enum class Permission : GrantedAuthority { const val READ_STR = "ROLE_READ" const val UPLOAD_STR = "ROLE_UPLOAD" const val WRITE_STR = "ROLE_WRITE" + const val ACCESS_ADMIN_PANEL_STR = "ROLE_ACCESS_ADMIN_PANEL" const val LIST_HIDDENFILES_STR = "ROLE_LIST_HIDDEN_FILES" const val MANAGE_USERS_STR = "ROLE_MANAGE_USERS" } diff --git a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/persistence/entities/Role.kt b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/persistence/entities/Role.kt index 5556a10..6969eb7 100644 --- a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/persistence/entities/Role.kt +++ b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/persistence/entities/Role.kt @@ -3,8 +3,9 @@ package pl.edu.uj.ii.ksi.mordor.persistence.entities enum class Role(val permissions: List) { NOBODY(listOf()), USER(listOf(Permission.ROLE_READ, Permission.ROLE_UPLOAD)), - MOD(listOf(Permission.ROLE_READ, Permission.ROLE_UPLOAD, Permission.ROLE_WRITE, Permission.ROLE_LIST_HIDDEN_FILES)), + MOD(listOf(Permission.ROLE_READ, Permission.ROLE_UPLOAD, Permission.ROLE_WRITE, Permission.ROLE_LIST_HIDDEN_FILES, + Permission.ROLE_ACCESS_ADMIN_PANEL)), ADMIN(listOf(Permission.ROLE_READ, Permission.ROLE_UPLOAD, Permission.ROLE_WRITE, Permission.ROLE_LIST_HIDDEN_FILES, - Permission.ROLE_MANAGE_USERS)), + Permission.ROLE_ACCESS_ADMIN_PANEL, Permission.ROLE_MANAGE_USERS)), EXTERNAL(listOf()) } diff --git a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/services/UserManagerService.kt b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/services/UserManagerService.kt index 40fe558..98cf1be 100644 --- a/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/services/UserManagerService.kt +++ b/src/main/kotlin/pl/edu/uj/ii/ksi/mordor/services/UserManagerService.kt @@ -11,7 +11,7 @@ class UserManagerService( private val userRepository: UserRepository, private val tokenRepository: EmailVerificationTokenRepository ) { - private fun hashPassword(password: String): String = "{bcrypt}" + BCryptPasswordEncoder().encode(password) + fun hashPassword(password: String): String = "{bcrypt}" + BCryptPasswordEncoder().encode(password) fun resetPassword(tokenId: String, password: String): Boolean { val token = tokenRepository.findByToken(tokenId) diff --git a/src/main/resources/templates/admin/layout_admin.html b/src/main/resources/templates/admin/layout_admin.html new file mode 100644 index 0000000..8c6fa26 --- /dev/null +++ b/src/main/resources/templates/admin/layout_admin.html @@ -0,0 +1,21 @@ + + + + + Mordor - Admin + + +
+ +
+ +
+
+ + diff --git a/src/main/resources/templates/admin/user_create.html b/src/main/resources/templates/admin/user_create.html new file mode 100644 index 0000000..14707b2 --- /dev/null +++ b/src/main/resources/templates/admin/user_create.html @@ -0,0 +1,60 @@ + + + + + Create user + + +
+
+
+ + +
error
+
+
+ + +
error
+
+
+ + +
error +
+
+
+ + +
error
+
+
+ + +
error
+
+
+ + +
error +
+
+
+ + +
+
+ + +
+ +
+
+ + + diff --git a/src/main/resources/templates/admin/user_list.html b/src/main/resources/templates/admin/user_list.html new file mode 100644 index 0000000..cf35a36 --- /dev/null +++ b/src/main/resources/templates/admin/user_list.html @@ -0,0 +1,43 @@ + + + + + Users + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
IdUsernameEmailFirst nameLast nameLocal loginRole
+
+
+ Add +
+ + + diff --git a/src/main/resources/templates/admin/user_profile.html b/src/main/resources/templates/admin/user_profile.html new file mode 100644 index 0000000..925ed75 --- /dev/null +++ b/src/main/resources/templates/admin/user_profile.html @@ -0,0 +1,84 @@ + + + + + User profile + + +
+
+ + +
+

username

+
+
+ + +
error
+
+
+ + +
error +
+
+
+ + +
error
+
+
+ + +
error
+
+
+ + +
error +
+
+
+ + +
+
+ + +
+ +
+ + +
+ + + diff --git a/src/main/resources/templates/fragments/pagination.html b/src/main/resources/templates/fragments/pagination.html new file mode 100644 index 0000000..a454a9d --- /dev/null +++ b/src/main/resources/templates/fragments/pagination.html @@ -0,0 +1,27 @@ + + + + + Pagination + + + + + diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html index 603b318..1ee851d 100644 --- a/src/main/resources/templates/layout.html +++ b/src/main/resources/templates/layout.html @@ -52,16 +52,20 @@ - - - +