Skip to content
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
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ dependencies {
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("org.springframework.security:spring-security-test")
runtimeOnly("com.h2database:h2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import site.billilge.api.backend.domain.item.dto.request.ItemRequest
import site.billilge.api.backend.domain.item.dto.response.AdminItemFindAllResponse
import site.billilge.api.backend.domain.item.dto.response.ItemDetail
import site.billilge.api.backend.domain.item.facade.AdminItemFacade
import site.billilge.api.backend.domain.member.enums.Role
import site.billilge.api.backend.global.annotation.OnlyAdmin
import site.billilge.api.backend.global.dto.PageableCondition
import site.billilge.api.backend.global.dto.SearchCondition

@RestController
@RequestMapping("/admin/items")
@OnlyAdmin
@OnlyAdmin(roles = [Role.ADMIN, Role.GA, Role.WORKER])
class AdminItemController(
private val adminItemFacade: AdminItemFacade
) : AdminItemApi {
Expand All @@ -32,6 +33,7 @@ class AdminItemController(
)
}

@OnlyAdmin
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
override fun addItem(
@RequestPart image: MultipartFile,
Expand All @@ -41,6 +43,7 @@ class AdminItemController(
return ResponseEntity.status(HttpStatus.CREATED).build()
}

@OnlyAdmin
@PutMapping("/{itemId}", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
override fun updateItem(
@PathVariable itemId: Long,
Expand All @@ -58,6 +61,7 @@ class AdminItemController(
return ResponseEntity.ok(adminItemFacade.getItemById(itemId))
}

@OnlyAdmin
@DeleteMapping("/{itemId}")
override fun deleteItem(@PathVariable itemId: Long): ResponseEntity<Void> {
adminItemFacade.deleteItem(itemId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import site.billilge.api.backend.domain.member.dto.request.AdminRequest
import site.billilge.api.backend.domain.member.dto.response.AdminFindAllResponse
import site.billilge.api.backend.domain.member.dto.response.MemberFindAllResponse
import site.billilge.api.backend.domain.member.facade.AdminMemberFacade
import site.billilge.api.backend.domain.member.enums.Role
import site.billilge.api.backend.global.annotation.OnlyAdmin
import site.billilge.api.backend.global.dto.PageableCondition
import site.billilge.api.backend.global.dto.SearchCondition

@RestController
@RequestMapping("admin/members")
@OnlyAdmin
@OnlyAdmin(roles = [Role.ADMIN, Role.GA, Role.WORKER])
class AdminMemberController(
private val adminMemberFacade: AdminMemberFacade
) : AdminMemberApi {
Expand All @@ -32,12 +33,14 @@ class AdminMemberController(
return ResponseEntity.ok(adminMemberFacade.getAdminList(pageableCondition, searchCondition))
}

@OnlyAdmin
@PostMapping("/admins")
override fun addAdmins(@RequestBody request: AdminRequest): ResponseEntity<Void> {
adminMemberFacade.addAdmins(request)
return ResponseEntity.ok().build()
}

@OnlyAdmin
@DeleteMapping("/admins")
override fun deleteAdmins(@RequestBody request: AdminRequest): ResponseEntity<Void> {
adminMemberFacade.deleteAdmins(request)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package site.billilge.api.backend.domain.member.dto.request

import io.swagger.v3.oas.annotations.media.Schema
import site.billilge.api.backend.domain.member.enums.Role

@Schema
data class AdminRequest(
@field:Schema(description = "회원 ID 목록", example = "[1, 2, 3]")
val memberIds: List<Long>
val memberIds: List<Long>,
@field:Schema(description = "관리자 역할", example = "ADMIN")
val role: Role = Role.ADMIN,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ enum class Role(
val description: String,
) {
USER("ROLE_USER", "사용자"),
ADMIN("ROLE_ADMIN", "관리자")
ADMIN("ROLE_ADMIN", "관리자"),
WORKER("ROLE_WORKER", "근무자"),
GA("ROLE_GA", "총무부"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class AdminMemberFacade(
}

fun addAdmins(request: AdminRequest) {
memberService.addAdmins(request.memberIds)
memberService.addAdmins(request.memberIds, request.role)
}

fun deleteAdmins(request: AdminRequest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ class MemberService(
}

@Transactional
fun addAdmins(memberIds: List<Long>) {
fun addAdmins(memberIds: List<Long>, role: Role) {
memberRepository.findAllByIds(memberIds)
.forEach { member ->
member.updateRole(Role.ADMIN)
member.updateRole(role)
}
}

Expand Down Expand Up @@ -125,7 +125,7 @@ class MemberService(
if (password != adminPassword)
throw ApiException(MemberErrorCode.ADMIN_PASSWORD_MISMATCH)

if (member.role != Role.ADMIN)
if (member.role !in listOf(Role.ADMIN, Role.GA, Role.WORKER))
throw ApiException(MemberErrorCode.FORBIDDEN)

return tokenProvider.generateToken(member, Duration.ofDays(30))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import site.billilge.api.backend.domain.notification.dto.response.NotificationFindAllResponse
import site.billilge.api.backend.domain.notification.facade.AdminNotificationFacade
import site.billilge.api.backend.domain.member.enums.Role
import site.billilge.api.backend.global.annotation.OnlyAdmin
import site.billilge.api.backend.global.security.oauth2.UserAuthInfo

@OnlyAdmin
@OnlyAdmin(roles = [Role.ADMIN, Role.GA, Role.WORKER])
@RestController
@RequestMapping("/admin/notifications")
class AdminNotificationController(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import site.billilge.api.backend.domain.payer.dto.request.PayerDeleteRequest
import site.billilge.api.backend.domain.payer.dto.request.PayerRequest
import site.billilge.api.backend.domain.payer.dto.response.PayerFindAllResponse
import site.billilge.api.backend.domain.payer.facade.AdminPayerFacade
import site.billilge.api.backend.domain.member.enums.Role
import site.billilge.api.backend.global.annotation.OnlyAdmin
import site.billilge.api.backend.global.dto.PageableCondition
import site.billilge.api.backend.global.dto.SearchCondition
Expand All @@ -18,7 +19,7 @@ import java.time.format.DateTimeFormatter

@RestController
@RequestMapping("/admin/members/payers")
@OnlyAdmin
@OnlyAdmin(roles = [Role.ADMIN, Role.GA])
class AdminPayerController(
private val adminPayerFacade: AdminPayerFacade
) : AdminPayerApi {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import site.billilge.api.backend.domain.rental.dto.response.AdminRentalHistoryFi
import site.billilge.api.backend.domain.rental.dto.response.DashboardResponse
import site.billilge.api.backend.domain.rental.enums.RentalStatus
import site.billilge.api.backend.domain.rental.facade.AdminRentalFacade
import site.billilge.api.backend.domain.member.enums.Role
import site.billilge.api.backend.global.annotation.OnlyAdmin
import site.billilge.api.backend.global.dto.PageableCondition
import site.billilge.api.backend.global.dto.SearchCondition
import site.billilge.api.backend.global.security.oauth2.UserAuthInfo

@RestController
@RequestMapping("/admin/rentals")
@OnlyAdmin
@OnlyAdmin(roles = [Role.ADMIN, Role.GA, Role.WORKER])
class AdminRentalController(
private val adminRentalFacade: AdminRentalFacade
) : AdminRentalApi {
Expand Down Expand Up @@ -53,6 +54,7 @@ class AdminRentalController(
return ResponseEntity.ok().build()
}

@OnlyAdmin
@DeleteMapping("/{rentalHistoryId}")
override fun deleteRentalHistory(@PathVariable rentalHistoryId: Long): ResponseEntity<Void> {
adminRentalFacade.deleteRentalHistory(rentalHistoryId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import site.billilge.api.backend.domain.rental.dto.response.RentalHistoryFindAll
import site.billilge.api.backend.domain.rental.dto.response.ReturnRequiredItemFindAllResponse
import site.billilge.api.backend.domain.rental.enums.RentalStatus
import site.billilge.api.backend.domain.rental.facade.RentalFacade
import site.billilge.api.backend.global.annotation.OnlyAdmin
import site.billilge.api.backend.global.security.oauth2.UserAuthInfo

@RestController
Expand All @@ -27,6 +28,7 @@ class RentalController(
return ResponseEntity.status(HttpStatus.CREATED).build()
}

@OnlyAdmin
@PostMapping("/dev")
override fun createDevRental(
@AuthenticationPrincipal userAuthInfo: UserAuthInfo,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package site.billilge.api.backend.global.annotation

import org.springframework.security.access.prepost.PreAuthorize
import site.billilge.api.backend.domain.member.enums.Role

@Target(
AnnotationTarget.FUNCTION,
Expand All @@ -11,5 +11,6 @@ import org.springframework.security.access.prepost.PreAuthorize
@Retention(
AnnotationRetention.RUNTIME
)
@PreAuthorize("hasRole('ROLE_ADMIN')")
annotation class OnlyAdmin
annotation class OnlyAdmin(
val roles: Array<Role> = [Role.ADMIN]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package site.billilge.api.backend.global.annotation

import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.security.authorization.AuthorizationDeniedException
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Component

@Aspect
@Component
class OnlyAdminAspect {

@Before("@within(site.billilge.api.backend.global.annotation.OnlyAdmin) || @annotation(site.billilge.api.backend.global.annotation.OnlyAdmin)")
fun checkRole(joinPoint: JoinPoint) {
val methodSignature = joinPoint.signature as MethodSignature
val method = methodSignature.method

val annotation = method.getAnnotation(OnlyAdmin::class.java)
?: joinPoint.target.javaClass.getAnnotation(OnlyAdmin::class.java)
?: return

val allowedRoles = annotation.roles.map { it.key }.toSet()

val authentication = SecurityContextHolder.getContext().authentication
val authorities = authentication?.authorities?.map { it.authority }?.toSet() ?: emptySet()

if (authorities.none { it in allowedRoles }) {
throw AuthorizationDeniedException("Access Denied")
}
}
}
Loading