Prevents Hunters from accessing hunt information before it starts

This commit is contained in:
2026-05-18 11:41:22 -05:00
parent 08d0b1730a
commit 8ff73cda2b
4 changed files with 39 additions and 12 deletions

View File

@@ -13,6 +13,7 @@ import net.halfbinary.scavengerhuntapi.model.response.ItemResponse
import net.halfbinary.scavengerhuntapi.service.HuntService import net.halfbinary.scavengerhuntapi.service.HuntService
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping import org.springframework.web.bind.annotation.PatchMapping
@@ -27,8 +28,8 @@ import org.springframework.web.bind.annotation.RestController
class ItemController(private val huntService: HuntService) { class ItemController(private val huntService: HuntService) {
@GetMapping @GetMapping
fun getItemsForHunt(@PathVariable huntId: HuntId): ResponseEntity<List<ItemResponse>> { fun getItemsForHunt(@PathVariable huntId: HuntId, authentication: Authentication): ResponseEntity<List<ItemResponse>> {
return ResponseEntity.ok(huntService.getItemsForHunt(huntId).map { it.toResponse() }) return ResponseEntity.ok(huntService.getItemsForHunt(huntId, authentication.name).map { it.toResponse() })
} }
@GetMapping("/{itemId}") @GetMapping("/{itemId}")

View File

@@ -10,4 +10,7 @@ data class Hunt(
val startDateTime: LocalDateTime, val startDateTime: LocalDateTime,
val endDateTime: LocalDateTime, val endDateTime: LocalDateTime,
val isTerminated: Boolean val isTerminated: Boolean
) ) {
val isOngoing: Boolean
get() = !isTerminated && startDateTime < LocalDateTime.now() && endDateTime > LocalDateTime.now()
}

View File

@@ -1,5 +1,6 @@
package net.halfbinary.scavengerhuntapi.service package net.halfbinary.scavengerhuntapi.service
import net.halfbinary.scavengerhuntapi.error.exception.ForbiddenException
import net.halfbinary.scavengerhuntapi.error.exception.NotFoundException import net.halfbinary.scavengerhuntapi.error.exception.NotFoundException
import net.halfbinary.scavengerhuntapi.model.HuntId import net.halfbinary.scavengerhuntapi.model.HuntId
import net.halfbinary.scavengerhuntapi.model.HunterId import net.halfbinary.scavengerhuntapi.model.HunterId
@@ -22,7 +23,8 @@ import java.time.LocalDateTime
class HuntService( class HuntService(
private val huntRepository: HuntRepository, private val huntRepository: HuntRepository,
private val itemRepository: ItemRepository, private val itemRepository: ItemRepository,
private val huntItemRepository: HuntItemRepository private val huntItemRepository: HuntItemRepository,
private val hunterService: HunterService
) { ) {
fun getHunt(huntId: HuntId): Hunt { fun getHunt(huntId: HuntId): Hunt {
return huntRepository.findByIdOrNull(huntId)?.toDomain() ?: throw NotFoundException("No hunt with id $huntId found") return huntRepository.findByIdOrNull(huntId)?.toDomain() ?: throw NotFoundException("No hunt with id $huntId found")
@@ -66,8 +68,10 @@ class HuntService(
return huntRepository.save(hunt.toRecord()).toDomain() return huntRepository.save(hunt.toRecord()).toDomain()
} }
fun getItemsForHunt(huntId: HuntId): List<Item> { fun getItemsForHunt(huntId: HuntId, email: String): List<Item> {
huntRepository.findByIdOrNull(huntId) ?: throw NotFoundException("No hunt with id $huntId found") val hunt = huntRepository.findByIdOrNull(huntId)?.toDomain() ?: throw NotFoundException("No hunt with id $huntId found")
val hunter = hunterService.getHunterByEmail(email)
if (!hunter.isAdmin && !hunt.isOngoing) throw ForbiddenException()
return itemRepository.findAllByHuntId(huntId).map { it.toDomain() } return itemRepository.findAllByHuntId(huntId).map { it.toDomain() }
} }

View File

@@ -36,10 +36,15 @@ class PhotoService(
private val photoRepository: PhotoRepository, private val photoRepository: PhotoRepository,
private val hunterService: HunterService, private val hunterService: HunterService,
private val teamService: TeamService, private val teamService: TeamService,
private val huntService: HuntService,
private val s3StorageService: S3StorageService, private val s3StorageService: S3StorageService,
private val fileProbeService: FileProbeService private val fileProbeService: FileProbeService
) { ) {
fun submitPhoto(huntId: HuntId, itemId: ItemId, email: String, file: MultipartFile) { fun submitPhoto(huntId: HuntId, itemId: ItemId, email: String, file: MultipartFile) {
val hunter = hunterService.getHunterByEmail(email)
val hunt = huntService.getHunt(huntId)
if (!hunter.isAdmin && !hunt.isOngoing) throw ForbiddenException()
val originalBytes = file.bytes val originalBytes = file.bytes
val fileType = fileProbeService.getFileType(originalBytes) val fileType = fileProbeService.getFileType(originalBytes)
@@ -51,7 +56,6 @@ class PhotoService(
throw BadFileException("Image type is not supported") throw BadFileException("Image type is not supported")
} }
val hunter = hunterService.getHunterByEmail(email)
val now = LocalDateTime.now() val now = LocalDateTime.now()
val photo = Photo( val photo = Photo(
itemId = itemId, itemId = itemId,
@@ -76,6 +80,8 @@ class PhotoService(
?: throw NotFoundException(PHOTO_NOT_FOUND) ?: throw NotFoundException(PHOTO_NOT_FOUND)
if (!requestingHunter.isAdmin) { if (!requestingHunter.isAdmin) {
val hunt = huntService.getHunt(huntId)
if (!hunt.isOngoing) throw ForbiddenException()
val team = try { val team = try {
teamService.getTeamForHunterInHunt(huntId, email) teamService.getTeamForHunterInHunt(huntId, email)
} catch (_: NotFoundException) { } catch (_: NotFoundException) {
@@ -121,6 +127,8 @@ class PhotoService(
val requestingHunter = hunterService.getHunterByEmail(email) val requestingHunter = hunterService.getHunterByEmail(email)
if (!requestingHunter.isAdmin) { if (!requestingHunter.isAdmin) {
val hunt = huntService.getHunt(huntId)
if (!hunt.isOngoing) throw ForbiddenException()
val team = try { val team = try {
teamService.getTeamForHunterInHunt(huntId, email) teamService.getTeamForHunterInHunt(huntId, email)
} catch (_: NotFoundException) { } catch (_: NotFoundException) {
@@ -142,15 +150,24 @@ class PhotoService(
} }
fun removePhoto(huntId: HuntId, teamId: TeamId, itemId: ItemId, photoId: PhotoId, email: String) { fun removePhoto(huntId: HuntId, teamId: TeamId, itemId: ItemId, photoId: PhotoId, email: String) {
val requestingHunter = hunterService.getHunterByEmail(email)
if (!requestingHunter.isAdmin) {
val hunt = huntService.getHunt(huntId)
if (!hunt.isOngoing) throw ForbiddenException()
}
val photoRecord = photoRepository.findByIdAndItemIdAndHuntId(photoId, itemId, huntId) val photoRecord = photoRepository.findByIdAndItemIdAndHuntId(photoId, itemId, huntId)
?: throw NotFoundException(PHOTO_NOT_FOUND) ?: throw NotFoundException(PHOTO_NOT_FOUND)
val team = try { if (!requestingHunter.isAdmin) {
teamService.getTeamForHunterInHunt(huntId, email) val team = try {
} catch (_: NotFoundException) { teamService.getTeamForHunterInHunt(huntId, email)
throw ForbiddenException() } catch (_: NotFoundException) {
throw ForbiddenException()
}
if (team.id != teamId) throw ForbiddenException()
} }
if (team.id != teamId) throw ForbiddenException()
if (photoRecord.status == PhotoStatus.APPROVED) throw ConflictException("Cannot remove an approved photo") if (photoRecord.status == PhotoStatus.APPROVED) throw ConflictException("Cannot remove an approved photo")
@@ -161,6 +178,8 @@ class PhotoService(
val requestingHunter = hunterService.getHunterByEmail(email) val requestingHunter = hunterService.getHunterByEmail(email)
if (!requestingHunter.isAdmin) { if (!requestingHunter.isAdmin) {
val hunt = huntService.getHunt(huntId)
if (!hunt.isOngoing) throw ForbiddenException()
val team = try { val team = try {
teamService.getTeamForHunterInHunt(huntId, email) teamService.getTeamForHunterInHunt(huntId, email)
} catch (_: NotFoundException) { } catch (_: NotFoundException) {