Changed logins to have proper redirections

This commit is contained in:
neviyn 2021-05-10 22:49:58 +01:00
parent 1bce01e07e
commit 20b9dcf1d5
5 changed files with 138 additions and 66 deletions

View File

@ -3,6 +3,7 @@ package uk.co.neviyn.booru
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.Page import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.security.access.annotation.Secured
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
import org.springframework.ui.Model import org.springframework.ui.Model
@ -33,38 +34,68 @@ class BaseController
} }
@GetMapping("/login") @GetMapping("/login")
fun login(model: Model, error: String?, logout: String?): String? { fun login(model: Model, error: String?, logout: String?, @RequestParam redirect: String): String? {
if (error != null) model.addAttribute("error", "Your username and password is invalid.") if (error != null) model.addAttribute("error", "Your username and password is invalid.")
if (logout != null) model.addAttribute("message", "You have been logged out successfully.") if (logout != null) model.addAttribute("message", "You have been logged out successfully.")
model.addAttribute("redirect", redirect)
return "login" return "login"
} }
} }
fun userCanEdit(userDetails: CustomUserDetails?, image: Image): Boolean =
userDetails != null && (userDetails.authorities.any { it.authority == "ADMIN" } || userDetails.getId() == image.uploader.id)
@Controller @Controller
@RequestMapping("/view/{id}") @RequestMapping("/view/{id}")
class SingleImageViewController class SingleImageViewController
@Autowired constructor( @Autowired constructor(
val imageRepository: ImageRepository, val imageRepository: ImageRepository,
val tagRepository: TagRepository val tagRepository: TagRepository
){ ) {
@GetMapping @GetMapping
fun viewSingleImage(@PathVariable id: Long, model: Model, @AuthenticationPrincipal userDetails: CustomUserDetails?) : String { fun viewSingleImage(@PathVariable id: Long, model: Model, @AuthenticationPrincipal userDetails: CustomUserDetails?): String {
val image = imageRepository.findById(id).get() val image = imageRepository.findById(id).get()
val tagData = image.tags.sortedBy { it.tag } val tagData = image.tags.sortedBy { it.tag }
model.addAttribute("image", image) model.addAttribute("image", image)
model.addAttribute("tags", tagData) model.addAttribute("tags", tagData)
model.addAttribute("title", tagData.joinToString { it.tag }) model.addAttribute("title", tagData.joinToString { it.tag })
model.addAttribute("isUploader", (userDetails != null && (userDetails.authorities.any { it.authority == "ADMIN" } || userDetails.getId() == image.uploader.id))) model.addAttribute("isUploader", userCanEdit(userDetails, image))
return "single" return "single"
} }
@PostMapping("/removetag") @PostMapping("/addtag")
@Secured
@Transactional @Transactional
fun removeTagFromImage(@PathVariable id: Long, @RequestParam("tagId") tagID: Long, model: Model, @AuthenticationPrincipal userDetails: CustomUserDetails?) : String { fun addTagToImage(
@PathVariable id: Long,
@RequestParam("tagName") tagName: String,
model: Model,
@AuthenticationPrincipal userDetails: CustomUserDetails?
): String {
val image = imageRepository.findById(id).get() val image = imageRepository.findById(id).get()
if(userDetails == null || userDetails.getId() != image.uploader.id || !userDetails.authorities.any { it.authority == "ADMIN" }) return "redirect:/view/$id" if (!userCanEdit(userDetails, image)) return "redirect:/view/$id"
var newTag = tagRepository.findByTagIs(tagName) ?: Tag(tag = tagName)
if (image.tags.contains(newTag)) return "redirect:/view/$id" // Don't add a tag that an image already has
newTag.amount += 1
newTag = tagRepository.save(newTag)
image.tags.add(newTag)
imageRepository.save(image)
return "redirect:/view/$id"
}
@PostMapping("/removetag")
@Secured
@Transactional
fun removeTagFromImage(
@PathVariable id: Long,
@RequestParam("tagId") tagID: Long,
model: Model,
@AuthenticationPrincipal userDetails: CustomUserDetails?
): String {
val image = imageRepository.findById(id).get()
if (!userCanEdit(userDetails, image)) return "redirect:/view/$id"
val targetTag = image.tags.find { it.id == tagID } val targetTag = image.tags.find { it.id == tagID }
if(targetTag != null){ if (targetTag != null) {
targetTag.amount -= 1 targetTag.amount -= 1
tagRepository.save(targetTag) tagRepository.save(targetTag)
image.tags.remove(targetTag) image.tags.remove(targetTag)
@ -127,13 +158,15 @@ class MemberController
) { ) {
@GetMapping @GetMapping
fun memberDetails(@AuthenticationPrincipal userDetails: CustomUserDetails, model: Model) : String { fun memberDetails(@AuthenticationPrincipal userDetails: CustomUserDetails?, model: Model): String {
if (userDetails == null) return "redirect:/login?redirect=/user"
val user = DisplayUser(userDetails.getId(), userDetails.username, userDetails.getEmail(), "", "") val user = DisplayUser(userDetails.getId(), userDetails.username, userDetails.getEmail(), "", "")
model.addAttribute("userData", user) model.addAttribute("userData", user)
return "user" return "user"
} }
@PostMapping @PostMapping
@Secured
fun updateLoggedInUser(@Valid @ModelAttribute userData: DisplayUser, @AuthenticationPrincipal userDetails: CustomUserDetails, model: Model): String { fun updateLoggedInUser(@Valid @ModelAttribute userData: DisplayUser, @AuthenticationPrincipal userDetails: CustomUserDetails, model: Model): String {
if (userData.id == userDetails.getId() && passwordEncoder().matches(userData.oldPassword, userDetails.password)) { if (userData.id == userDetails.getId() && passwordEncoder().matches(userData.oldPassword, userDetails.password)) {
val user = memberRepository.findById(userDetails.getId()).get() val user = memberRepository.findById(userDetails.getId()).get()
@ -162,9 +195,13 @@ class UploadController
) { ) {
@GetMapping @GetMapping
fun showUploadPage(): String = "upload" fun showUploadPage(@AuthenticationPrincipal userDetails: CustomUserDetails?): String {
if (userDetails == null) return "redirect:/login?redirect=/upload"
return "upload"
}
@PostMapping @PostMapping
@Secured
fun uploadFile( fun uploadFile(
@AuthenticationPrincipal userDetails: CustomUserDetails, @AuthenticationPrincipal userDetails: CustomUserDetails,
@RequestParam file: MultipartFile, @RequestParam file: MultipartFile,
@ -183,12 +220,13 @@ class UploadController
} }
@DeleteMapping("/d/{imageID}") @DeleteMapping("/d/{imageID}")
@Secured
@Transactional @Transactional
fun deleteUpload(@PathVariable imageID: Long, @AuthenticationPrincipal userDetails: CustomUserDetails) : String { fun deleteUpload(@PathVariable imageID: Long, @AuthenticationPrincipal userDetails: CustomUserDetails): String {
val target = imageRepository.findById(imageID) val target = imageRepository.findById(imageID)
if(target.isEmpty) return "gallery" // No image with ID if (target.isEmpty) return "gallery" // No image with ID
val rTarget = target.get() val rTarget = target.get()
return if(userDetails.authorities.any { it.authority == "ADMIN" } || rTarget.uploader.id == userDetails.getId()) { return if (userDetails.authorities.any { it.authority == "ADMIN" } || rTarget.uploader.id == userDetails.getId()) {
rTarget.tags.map { it.amount = it.amount - 1 } rTarget.tags.map { it.amount = it.amount - 1 }
tagRepository.saveAll(rTarget.tags) tagRepository.saveAll(rTarget.tags)
imageRepository.deleteTagAssociations(rTarget.id) imageRepository.deleteTagAssociations(rTarget.id)

View File

@ -13,7 +13,12 @@ import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
@Configuration @Configuration
class SecurityConfig class SecurityConfig
@ -21,9 +26,9 @@ class SecurityConfig
val userDetailsService: CustomUserDetailsService val userDetailsService: CustomUserDetailsService
) : WebSecurityConfigurerAdapter() { ) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) { override fun configure(http: HttpSecurity) {
http.authorizeRequests().antMatchers("/upload/**", "/user/**").hasAuthority("USER") http.authorizeRequests()
.anyRequest().permitAll().and() .anyRequest().permitAll().and()
.formLogin().loginPage("/login").permitAll().and() .formLogin().successHandler(RefererRedirectionAuthenticationSuccessHandler()).loginPage("/login").permitAll().and()
.logout().logoutSuccessUrl("/").permitAll().and() .logout().logoutSuccessUrl("/").permitAll().and()
.httpBasic() .httpBasic()
} }
@ -66,3 +71,11 @@ class CustomUserDetailsService
@Bean @Bean
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
// Keeps us on the same page when logging in
class RefererRedirectionAuthenticationSuccessHandler : SimpleUrlAuthenticationSuccessHandler(), AuthenticationSuccessHandler {
override fun determineTargetUrl(request: HttpServletRequest?, response: HttpServletResponse?): String {
return request!!.getParameter("redirect")
}
}

View File

@ -1,58 +1,73 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <html lang="en" xmlns:sec="http://www.thymeleaf.org/extras/spring-security" xmlns:th="http://www.thymeleaf.org">
<body> <body>
<div th:fragment="header" th:remove="tag"> <div th:fragment="header" th:remove="tag">
<meta charset="UTF-8"> <meta charset="UTF-8">
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/svg+xml" th:href="@{/webjars/bootstrap-icons/1.4.1/icons/images.svg}"> <link rel="icon" th:href="@{/webjars/bootstrap-icons/1.4.1/icons/images.svg}" type="image/svg+xml">
<!-- Common styles and scripts --> <!-- Common styles and scripts -->
<link th:href="@{/webjars/bootstrap/5.0.0-beta3/css/bootstrap.min.css}" rel="stylesheet"> <link rel="stylesheet" th:href="@{/webjars/bootstrap/5.0.0-beta3/css/bootstrap.min.css}">
<script th:src="@{/webjars/bootstrap/5.0.0-beta3/js/bootstrap.bundle.min.js}"></script> <script th:src="@{/webjars/bootstrap/5.0.0-beta3/js/bootstrap.bundle.min.js}"></script>
<link th:href="@{/webjars/bootstrap-icons/1.4.1/font/bootstrap-icons.css}" rel="stylesheet"> <link rel="stylesheet" th:href="@{/webjars/bootstrap-icons/1.4.1/font/bootstrap-icons.css}">
</div> </div>
<div th:fragment="navbar"> <div th:fragment="navbar">
<form id="logoutForm" method="POST" th:action="@{/logout}"> <form id="logoutForm" method="POST" th:action="@{/logout}">
<input name="${_csrf.parameterName}" type="hidden" value="${_csrf.token}"/> <input name="${_csrf.parameterName}" type="hidden" value="${_csrf.token}"/>
</form> </form>
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="/"> <a class="navbar-brand" href="/">
Booru Booru
</a> </a>
<button aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler" <button aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler"
data-bs-target="#navbarNavAltMarkup" data-bs-toggle="collapse" type="button"> data-bs-target="#navbarNavAltMarkup" data-bs-toggle="collapse" type="button">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup"> <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav me-auto"> <div class="navbar-nav me-auto">
<a class="nav-link" th:href="${#mvc.url('IC#getGalleryPage').build()}">Posts</a> <a class="nav-link" th:href="${#mvc.url('IC#getGalleryPage').build()}">Posts</a>
<a class="nav-link" th:href="${#mvc.url('UC#showUploadPage').build()}">Upload</a> <a class="nav-link" th:href="${#mvc.url('UC#showUploadPage').build()}">Upload</a>
<a class="nav-link" th:href="${#mvc.url('MC#memberDetails').build()}">My Account</a> <a class="nav-link" th:href="${#mvc.url('MC#memberDetails').build()}">My Account</a>
</div>
<div class="d-flex" sec:authorize="isAuthenticated()">
<span class="navbar-text" sec:authentication="name"></span>
<a class="nav-link" onclick="document.forms['logoutForm'].submit()" style="cursor: pointer"><i
class="bi bi-box-arrow-right me-1"></i>Logout</a>
</div>
<form class="form-sign_in" method="POST" sec:authorize="!isAuthenticated()" th:action="@{/login}">
<div class="form-group d-flex flex-row align-items-center">
<input id="redirectURL" name="redirect" type="hidden" value="">
<!--suppress HtmlFormInputWithoutLabel -->
<input class="form-control form-control-sm" id="username" name="username" placeholder="Username" type="text">
<!--suppress HtmlFormInputWithoutLabel -->
<input class="form-control form-control-sm" id="password" name="password" placeholder="Password" type="password">
<input name="${_csrf.parameterName}" type="hidden" value="${_csrf.token}"/>
<button class="btn btn-primary btn-sm text-nowrap" type="submit">Log In</button>
</div>
</form>
</div>
</div> </div>
<div sec:authorize="isAuthenticated()" class="d-flex"> <script>
<span sec:authentication="name" class="navbar-text"></span> // Set logging in via this page to redirect back to this exact page
<a class="nav-link" onclick="document.forms['logoutForm'].submit()" style="cursor: pointer"><i document.getElementById('redirectURL').setAttribute('value', window.location.href)
class="bi bi-box-arrow-right me-1"></i>Logout</a> </script>
</div> </nav>
</div>
</div>
</nav>
</div> </div>
<div th:fragment="searchBarRow"> <div th:fragment="searchBarRow">
<form action="/gallery" class="text-center row row-cols-auto justify-content-center align-items-center mt-2"> <form action="/gallery" class="text-center row row-cols-auto justify-content-center align-items-center mt-2">
<div class="col-10 col-lg-6"> <div class="col-10 col-lg-6">
<!--suppress HtmlFormInputWithoutLabel --> <!--suppress HtmlFormInputWithoutLabel -->
<input class="form-control" id="imageSearch" <input class="form-control" id="imageSearch"
name="tags" name="tags"
pattern="[a-zA-Z0-9\s_]*" pattern="[a-zA-Z0-9\s_]*"
placeholder="Ex: blue_eyes smile" placeholder="Ex: blue_eyes smile"
th:value="${#request.getParameter('tags')}" th:value="${#request.getParameter('tags')}"
type="search"> type="search">
</div> </div>
<div class="col-2 col-lg-1 d-grid"> <div class="col-2 col-lg-1 d-grid">
<button class="btn btn-primary" type="submit">Search</button> <button class="btn btn-primary" type="submit">Search</button>
</div> </div>
</form> </form>
</div> </div>
</body> </body>
</html> </html>

View File

@ -16,6 +16,7 @@
<div class="col-8 text-center"> <div class="col-8 text-center">
<form class="form-sign_in" method="POST" th:action="@{/login}"> <form class="form-sign_in" method="POST" th:action="@{/login}">
<div class="form-group mt-3"> <div class="form-group mt-3">
<input name="redirect" th:value="${redirect}" type="hidden">
<span th:text="${message}"></span> <span th:text="${message}"></span>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input autofocus class="form-control form-control-lg" id="username" name="username" type="text"> <input autofocus class="form-control form-control-lg" id="username" name="username" type="text">
@ -39,7 +40,7 @@
</div> </div>
<div class="row justify-content-center mt-3"> <div class="row justify-content-center mt-3">
<div class="col text-center"> <div class="col text-center">
<a class="btn btn-secondary btn-lg" th:href="${#mvc.url('BC#landingPage').build()}" role="button">Home</a> <a class="btn btn-secondary btn-lg" role="button" th:href="${#mvc.url('BC#landingPage').build()}">Home</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -15,13 +15,18 @@
<a class="text-decoration-none" <a class="text-decoration-none"
th:href="${#mvc.url('IC#getGalleryPage').arg(1, tag.tag).build()}" th:href="${#mvc.url('IC#getGalleryPage').arg(1, tag.tag).build()}"
th:text="${tag.tag} + ' '">tag</a><span th:text="${tag.amount}"></span> th:text="${tag.tag} + ' '">tag</a><span th:text="${tag.amount}"></span>
<form th:action="${'/view/' + image.id + '/removetag'}" method="post" th:if="${isUploader}"> <form method="post" th:action="${'/view/' + image.id + '/removetag'}" th:if="${isUploader}">
<input type="hidden" th:value="${tag.id}" name="tagId"> <input name="tagId" th:value="${tag.id}" type="hidden">
<button type="submit">-</button> <button type="submit">-</button>
</form> </form>
</li> </li>
</ul> </ul>
<p>Uploaded by: <span th:text="${image.uploader.name}"></span></p> <p>Uploaded by: <span th:text="${image.uploader.name}"></span></p>
<form method="post" th:action="'/view/' + ${image.id} + '/addtag'" th:if="${isUploader}">
<!--suppress HtmlFormInputWithoutLabel -->
<input name="tagName" placeholder="Add a new tag" type="text">
<button type="submit">+</button>
</form>
<form th:action="'/upload/d/' + ${image.id}" <form th:action="'/upload/d/' + ${image.id}"
th:if="${isUploader}" th:if="${isUploader}"
th:method="delete"> th:method="delete">