Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 단건 조회 & 통합 검색 권한 체크 #217

Merged
merged 7 commits into from
Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.wafflestudio.csereal.common.utils

import com.wafflestudio.csereal.common.CserealException
import com.wafflestudio.csereal.common.mockauth.CustomPrincipal
import org.jsoup.Jsoup
import org.jsoup.parser.Parser
import org.jsoup.safety.Safelist
import org.springframework.security.core.Authentication
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import kotlin.math.ceil

fun cleanTextFromHtml(description: String): String {
Expand Down Expand Up @@ -40,3 +44,15 @@ fun exchangeValidPageNum(pageSize: Int, pageNum: Int, total: Long): Int {
else -> ceil(total.toDouble() / pageSize).toInt()
}
}

fun getUsername(authentication: Authentication?): String? {
val principal = authentication?.principal

return principal?.let {
when (principal) {
is OidcUser -> principal.idToken.getClaim("username")
is CustomPrincipal -> principal.userEntity.username
else -> throw CserealException.Csereal401("Unsupported principal type")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.wafflestudio.csereal.core.news.api

import com.wafflestudio.csereal.common.CserealException
import com.wafflestudio.csereal.common.aop.AuthenticatedStaff
import com.wafflestudio.csereal.common.mockauth.CustomPrincipal
import com.wafflestudio.csereal.common.utils.getUsername
import com.wafflestudio.csereal.core.news.dto.NewsDto
import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse
import com.wafflestudio.csereal.core.news.dto.NewsTotalSearchDto
import com.wafflestudio.csereal.core.news.service.NewsService
import com.wafflestudio.csereal.core.user.database.Role
import com.wafflestudio.csereal.core.user.database.UserRepository
Expand All @@ -16,7 +16,6 @@ import org.springframework.data.domain.PageRequest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.core.Authentication
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile

Expand All @@ -34,15 +33,9 @@ class NewsController(
@RequestParam(required = false, defaultValue = "10") pageSize: Int,
authentication: Authentication?
): ResponseEntity<NewsSearchResponse> {
val principal = authentication?.principal

val isStaff = principal?.let {
val username = when (principal) {
is OidcUser -> principal.idToken.getClaim("username")
is CustomPrincipal -> principal.userEntity.username
else -> throw CserealException.Csereal401("Unsupported principal type")
}
val user = userRepository.findByUsername(username)
val username = getUsername(authentication)
val isStaff = username?.let {
val user = userRepository.findByUsername(it)
user?.role == Role.ROLE_STAFF
} ?: false

Expand All @@ -59,16 +52,29 @@ class NewsController(
@NotBlank
keyword: String,
@RequestParam(required = true) @Positive number: Int,
@RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int
) = ResponseEntity.ok(
newsService.searchTotalNews(keyword, number, stringLength)
)
@RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int,
authentication: Authentication?
): NewsTotalSearchDto {
val username = getUsername(authentication)
val isStaff = username?.let {
val user = userRepository.findByUsername(it)
user?.role == Role.ROLE_STAFF
} ?: false

return newsService.searchTotalNews(keyword, number, stringLength, isStaff)
}

@GetMapping("/{newsId}")
fun readNews(
@PathVariable newsId: Long
@PathVariable newsId: Long,
authentication: Authentication?
): ResponseEntity<NewsDto> {
return ResponseEntity.ok(newsService.readNews(newsId))
val username = getUsername(authentication)
val isStaff = username?.let {
val user = userRepository.findByUsername(it)
user?.role == Role.ROLE_STAFF
} ?: false
return ResponseEntity.ok(newsService.readNews(newsId, isStaff))
}

@AuthenticatedStaff
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ interface CustomNewsRepository {
keyword: String,
number: Int,
amount: Int,
imageUrlCreator: (MainImageEntity?) -> String?
imageUrlCreator: (MainImageEntity?) -> String?,
isStaff: Boolean
): NewsTotalSearchDto

fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse
Expand Down Expand Up @@ -135,14 +136,17 @@ class NewsRepositoryImpl(
keyword: String,
number: Int,
amount: Int,
imageUrlCreator: (MainImageEntity?) -> String?
imageUrlCreator: (MainImageEntity?) -> String?,
isStaff: Boolean
): NewsTotalSearchDto {
val doubleTemplate = commonRepository.searchFullDoubleTextTemplate(
keyword,
newsEntity.title,
newsEntity.plainTextDescription
)

val privateBoolean = newsEntity.isPrivate.eq(false).takeUnless { isStaff }

val searchResult = queryFactory.select(
newsEntity.id,
newsEntity.title,
Expand All @@ -151,7 +155,7 @@ class NewsRepositoryImpl(
mainImageEntity
).from(newsEntity)
.leftJoin(mainImageEntity)
.where(doubleTemplate.gt(0.0))
.where(doubleTemplate.gt(0.0), privateBoolean)
.orderBy(newsEntity.date.desc())
.limit(number.toLong())
.fetch()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface NewsService {
isStaff: Boolean
): NewsSearchResponse

fun readNews(newsId: Long): NewsDto
fun readNews(newsId: Long, isStaff: Boolean): NewsDto
fun createNews(request: NewsDto, mainImage: MultipartFile?, attachments: List<MultipartFile>?): NewsDto
fun updateNews(
newsId: Long,
Expand All @@ -34,7 +34,7 @@ interface NewsService {

fun deleteNews(newsId: Long)
fun enrollTag(tagName: String)
fun searchTotalNews(keyword: String, number: Int, amount: Int): NewsTotalSearchDto
fun searchTotalNews(keyword: String, number: Int, amount: Int, isStaff: Boolean): NewsTotalSearchDto
fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse
fun unSlideManyNews(request: List<Long>)
}
Expand Down Expand Up @@ -62,21 +62,25 @@ class NewsServiceImpl(
override fun searchTotalNews(
keyword: String,
number: Int,
amount: Int
amount: Int,
isStaff: Boolean
) = newsRepository.searchTotalNews(
keyword,
number,
amount,
mainImageService::createImageURL
mainImageService::createImageURL,
isStaff
)

@Transactional(readOnly = true)
override fun readNews(newsId: Long): NewsDto {
override fun readNews(newsId: Long, isStaff: Boolean): NewsDto {
val news: NewsEntity = newsRepository.findByIdOrNull(newsId)
?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId: $newsId)")

if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.(newsId: $newsId)")

if (news.isPrivate && !isStaff) throw CserealException.Csereal401("접근 권한이 없습니다.")

val imageURL = mainImageService.createImageURL(news.mainImage)
val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.wafflestudio.csereal.core.notice.api

import com.wafflestudio.csereal.common.CserealException
import com.wafflestudio.csereal.common.aop.AuthenticatedStaff
import com.wafflestudio.csereal.common.mockauth.CustomPrincipal
import com.wafflestudio.csereal.common.utils.getUsername
import com.wafflestudio.csereal.core.notice.dto.*
import com.wafflestudio.csereal.core.notice.service.NoticeService
import com.wafflestudio.csereal.core.user.database.Role
Expand All @@ -15,7 +14,6 @@ import org.springframework.data.domain.PageRequest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.core.Authentication
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile

Expand All @@ -33,15 +31,9 @@ class NoticeController(
@RequestParam(required = false, defaultValue = "20") pageSize: Int,
authentication: Authentication?
): ResponseEntity<NoticeSearchResponse> {
val principal = authentication?.principal

val isStaff = principal?.let {
val username = when (principal) {
is OidcUser -> principal.idToken.getClaim("username")
is CustomPrincipal -> principal.userEntity.username
else -> throw CserealException.Csereal401("Unsupported principal type")
}
val user = userRepository.findByUsername(username)
val username = getUsername(authentication)
val isStaff = username?.let {
val user = userRepository.findByUsername(it)
user?.role == Role.ROLE_STAFF
} ?: false

Expand All @@ -61,15 +53,9 @@ class NoticeController(
@RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int,
authentication: Authentication?
): NoticeTotalSearchResponse {
val principal = authentication?.principal

val isStaff = principal?.let {
val username = when (principal) {
is OidcUser -> principal.idToken.getClaim("username")
is CustomPrincipal -> principal.userEntity.username
else -> throw CserealException.Csereal401("Unsupported principal type")
}
val user = userRepository.findByUsername(username)
val username = getUsername(authentication)
val isStaff = username?.let {
val user = userRepository.findByUsername(it)
user?.role == Role.ROLE_STAFF
} ?: false

Expand All @@ -80,20 +66,13 @@ class NoticeController(
fun readNotice(
@PathVariable noticeId: Long,
authentication: Authentication?
): NoticeDto {
val principal = authentication?.principal

val isStaff = principal?.let {
val username = when (principal) {
is OidcUser -> principal.idToken.getClaim("username")
is CustomPrincipal -> principal.userEntity.username
else -> throw CserealException.Csereal401("Unsupported principal type")
}
val user = userRepository.findByUsername(username)
): ResponseEntity<NoticeDto> {
val username = getUsername(authentication)
val isStaff = username?.let {
val user = userRepository.findByUsername(it)
user?.role == Role.ROLE_STAFF
} ?: false

return noticeService.readNotice(noticeId, isStaff)
return ResponseEntity.ok(noticeService.readNotice(noticeId, isStaff))
}

@AuthenticatedStaff
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,13 @@ class NoticeServiceImpl(

@Transactional(readOnly = true)
override fun readNotice(noticeId: Long, isStaff: Boolean): NoticeDto {
val notice = if (isStaff) {
noticeRepository.findByIdOrNull(noticeId)
} else {
noticeRepository.findByIdAndIsPrivateFalse(noticeId)
} ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)")
val notice = noticeRepository.findByIdOrNull(noticeId)
?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)")

if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)")

if (notice.isPrivate && !isStaff) throw CserealException.Csereal401("접근 권한이 없습니다.")

val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments)

val prevNotice =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.wafflestudio.csereal.core.seminar.api

import com.wafflestudio.csereal.common.CserealException
import com.wafflestudio.csereal.common.aop.AuthenticatedStaff
import com.wafflestudio.csereal.common.mockauth.CustomPrincipal
import com.wafflestudio.csereal.common.utils.getUsername
import com.wafflestudio.csereal.core.seminar.dto.SeminarDto
import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse
import com.wafflestudio.csereal.core.seminar.service.SeminarService
Expand All @@ -12,7 +11,6 @@ import jakarta.validation.Valid
import org.springframework.data.domain.PageRequest
import org.springframework.http.ResponseEntity
import org.springframework.security.core.Authentication
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile

Expand All @@ -29,15 +27,9 @@ class SeminarController(
@RequestParam(required = false, defaultValue = "10") pageSize: Int,
authentication: Authentication?
): ResponseEntity<SeminarSearchResponse> {
val principal = authentication?.principal

val isStaff = principal?.let {
val username = when (principal) {
is OidcUser -> principal.idToken.getClaim("username")
is CustomPrincipal -> principal.userEntity.username
else -> throw CserealException.Csereal401("Unsupported principal type")
}
val user = userRepository.findByUsername(username)
val username = getUsername(authentication)
val isStaff = username?.let {
val user = userRepository.findByUsername(it)
user?.role == Role.ROLE_STAFF
} ?: false

Expand All @@ -61,9 +53,15 @@ class SeminarController(

@GetMapping("/{seminarId}")
fun readSeminar(
@PathVariable seminarId: Long
@PathVariable seminarId: Long,
authentication: Authentication?
): ResponseEntity<SeminarDto> {
return ResponseEntity.ok(seminarService.readSeminar(seminarId))
val username = getUsername(authentication)
val isStaff = username?.let {
val user = userRepository.findByUsername(it)
user?.role == Role.ROLE_STAFF
} ?: false
return ResponseEntity.ok(seminarService.readSeminar(seminarId, isStaff))
}

@AuthenticatedStaff
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface SeminarService {
): SeminarSearchResponse

fun createSeminar(request: SeminarDto, mainImage: MultipartFile?, attachments: List<MultipartFile>?): SeminarDto
fun readSeminar(seminarId: Long): SeminarDto
fun readSeminar(seminarId: Long, isStaff: Boolean): SeminarDto
fun updateSeminar(
seminarId: Long,
request: SeminarDto,
Expand Down Expand Up @@ -73,12 +73,14 @@ class SeminarServiceImpl(
}

@Transactional(readOnly = true)
override fun readSeminar(seminarId: Long): SeminarDto {
override fun readSeminar(seminarId: Long, isStaff: Boolean): SeminarDto {
val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId)
?: throw CserealException.Csereal404("존재하지 않는 세미나입니다.(seminarId: $seminarId)")

if (seminar.isDeleted) throw CserealException.Csereal400("삭제된 세미나입니다. (seminarId: $seminarId)")

if (seminar.isPrivate && !isStaff) throw CserealException.Csereal401("접근 권한이 없습니다.")

val imageURL = mainImageService.createImageURL(seminar.mainImage)
val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments)

Expand Down
Loading