feat(api): restrict page streaming and file download with roles

also add the ability to edit user roles

related to #146
This commit is contained in:
Gauthier Roebroeck 2020-06-10 14:43:24 +08:00
parent 327ed00857
commit 6291dab864
15 changed files with 157 additions and 38 deletions

View File

@ -0,0 +1,4 @@
alter table user
add column role_file_download boolean default true;
alter table user
add column role_page_streaming boolean default true;

View File

@ -4,6 +4,11 @@ import java.time.LocalDateTime
import javax.validation.constraints.Email
import javax.validation.constraints.NotBlank
const val ROLE_USER = "USER"
const val ROLE_ADMIN = "ADMIN"
const val ROLE_FILE_DOWNLOAD = "FILE_DOWNLOAD"
const val ROLE_PAGE_STREAMING = "PAGE_STREAMING"
data class KomgaUser(
@Email
@NotBlank
@ -11,6 +16,8 @@ data class KomgaUser(
@NotBlank
val password: String,
val roleAdmin: Boolean,
val roleFileDownload: Boolean = true,
val rolePageStreaming: Boolean = true,
val sharedLibrariesIds: Set<Long> = emptySet(),
val sharedAllLibraries: Boolean = true,
val id: Long = 0,
@ -19,8 +26,10 @@ data class KomgaUser(
) : Auditable() {
fun roles(): Set<String> {
val roles = mutableSetOf("USER")
if (roleAdmin) roles.add("ADMIN")
val roles = mutableSetOf(ROLE_USER)
if (roleAdmin) roles.add(ROLE_ADMIN)
if (roleFileDownload) roles.add(ROLE_FILE_DOWNLOAD)
if (rolePageStreaming) roles.add(ROLE_PAGE_STREAMING)
return roles
}
@ -52,4 +61,8 @@ data class KomgaUser(
fun canAccessLibrary(library: Library): Boolean {
return sharedAllLibraries || sharedLibrariesIds.any { it == library.id }
}
override fun toString(): String {
return "KomgaUser(email='$email', roleAdmin=$roleAdmin, roleFileDownload=$roleFileDownload, rolePageStreaming=$rolePageStreaming, sharedLibrariesIds=$sharedLibrariesIds, sharedAllLibraries=$sharedAllLibraries, id=$id, createdDate=$createdDate, lastModifiedDate=$lastModifiedDate)"
}
}

View File

@ -44,6 +44,8 @@ class KomgaUserDao(
email = ur.email,
password = ur.password,
roleAdmin = ur.roleAdmin,
roleFileDownload = ur.roleFileDownload,
rolePageStreaming = ur.rolePageStreaming,
sharedLibrariesIds = ulr.mapNotNull { it.libraryId }.toSet(),
sharedAllLibraries = ur.sharedAllLibraries,
id = ur.id,
@ -65,10 +67,12 @@ class KomgaUserDao(
.set(u.EMAIL, user.email)
.set(u.PASSWORD, user.password)
.set(u.ROLE_ADMIN, user.roleAdmin)
.set(u.ROLE_FILE_DOWNLOAD, user.roleFileDownload)
.set(u.ROLE_PAGE_STREAMING, user.rolePageStreaming)
.set(u.SHARED_ALL_LIBRARIES, user.sharedAllLibraries)
.set(u.LAST_MODIFIED_DATE, LocalDateTime.now())
.whenNotMatchedThenInsert(u.ID, u.EMAIL, u.PASSWORD, u.ROLE_ADMIN, u.SHARED_ALL_LIBRARIES)
.values(id, user.email, user.password, user.roleAdmin, user.sharedAllLibraries)
.whenNotMatchedThenInsert(u.ID, u.EMAIL, u.PASSWORD, u.ROLE_ADMIN, u.ROLE_FILE_DOWNLOAD, u.ROLE_PAGE_STREAMING, u.SHARED_ALL_LIBRARIES)
.values(id, user.email, user.password, user.roleAdmin, user.roleFileDownload, user.rolePageStreaming, user.sharedAllLibraries)
.execute()
deleteFrom(ul)

View File

@ -1,6 +1,8 @@
package org.gotson.komga.infrastructure.security
import mu.KotlinLogging
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.model.ROLE_USER
import org.gotson.komga.infrastructure.configuration.KomgaProperties
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
@ -37,16 +39,16 @@ class SecurityConfiguration(
.authorizeRequests()
// restrict all actuator endpoints to ADMIN only
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN")
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole(ROLE_ADMIN)
// restrict H2 console to ADMIN only
.requestMatchers(PathRequest.toH2Console()).hasRole("ADMIN")
.requestMatchers(PathRequest.toH2Console()).hasRole(ROLE_ADMIN)
// all other endpoints are restricted to authenticated users
.antMatchers(
"/api/**",
"/opds/**"
).hasRole("USER")
).hasRole(ROLE_USER)
// authorize frames for H2 console
.and()

View File

@ -12,6 +12,9 @@ import org.gotson.komga.domain.model.BookSearchWithReadProgress
import org.gotson.komga.domain.model.ImageConversionException
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.MediaNotReadyException
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.model.ROLE_FILE_DOWNLOAD
import org.gotson.komga.domain.model.ROLE_PAGE_STREAMING
import org.gotson.komga.domain.model.ReadStatus
import org.gotson.komga.domain.persistence.BookMetadataRepository
import org.gotson.komga.domain.persistence.BookRepository
@ -206,6 +209,7 @@ class BookController(
"api/v1/books/{bookId}/file/*",
"opds/v1.2/books/{bookId}/file/*"
], produces = [MediaType.APPLICATION_OCTET_STREAM_VALUE])
@PreAuthorize("hasRole('$ROLE_FILE_DOWNLOAD')")
fun getBookFile(
@AuthenticationPrincipal principal: KomgaPrincipal,
@PathVariable bookId: Long
@ -258,6 +262,7 @@ class BookController(
"api/v1/books/{bookId}/pages/{pageNumber}",
"opds/v1.2/books/{bookId}/pages/{pageNumber}"
])
@PreAuthorize("hasRole('$ROLE_PAGE_STREAMING')")
fun getBookPage(
@AuthenticationPrincipal principal: KomgaPrincipal,
request: WebRequest,
@ -345,7 +350,7 @@ class BookController(
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
@PostMapping("api/v1/books/{bookId}/analyze")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
@ResponseStatus(HttpStatus.ACCEPTED)
fun analyze(@PathVariable bookId: Long) {
bookRepository.findByIdOrNull(bookId)?.let { book ->
@ -354,7 +359,7 @@ class BookController(
}
@PostMapping("api/v1/books/{bookId}/metadata/refresh")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
@ResponseStatus(HttpStatus.ACCEPTED)
fun refreshMetadata(@PathVariable bookId: Long) {
bookRepository.findByIdOrNull(bookId)?.let { book ->
@ -363,7 +368,7 @@ class BookController(
}
@PatchMapping("api/v1/books/{bookId}/metadata")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
fun updateMetadata(
@PathVariable bookId: Long,
@Parameter(description = "Metadata fields to update. Set a field to null to unset the metadata. You can omit fields you don't want to update.")

View File

@ -6,6 +6,7 @@ import org.gotson.komga.domain.model.DirectoryNotFoundException
import org.gotson.komga.domain.model.DuplicateNameException
import org.gotson.komga.domain.model.Library
import org.gotson.komga.domain.model.PathContainedInPath
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.domain.persistence.LibraryRepository
import org.gotson.komga.domain.service.LibraryLifecycle
@ -59,7 +60,7 @@ class LibraryController(
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
fun addOne(
@AuthenticationPrincipal principal: KomgaPrincipal,
@Valid @RequestBody library: LibraryCreationDto
@ -78,7 +79,7 @@ class LibraryController(
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun deleteOne(@PathVariable id: Long) {
libraryRepository.findByIdOrNull(id)?.let {
@ -87,7 +88,7 @@ class LibraryController(
}
@PostMapping("{libraryId}/scan")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
@ResponseStatus(HttpStatus.ACCEPTED)
fun scan(@PathVariable libraryId: Long) {
libraryRepository.findByIdOrNull(libraryId)?.let { library ->
@ -96,7 +97,7 @@ class LibraryController(
}
@PostMapping("{libraryId}/analyze")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
@ResponseStatus(HttpStatus.ACCEPTED)
fun analyze(@PathVariable libraryId: Long) {
bookRepository.findAllIdByLibraryId(libraryId).forEach {
@ -105,7 +106,7 @@ class LibraryController(
}
@PostMapping("{libraryId}/metadata/refresh")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
@ResponseStatus(HttpStatus.ACCEPTED)
fun refreshMetadata(@PathVariable libraryId: Long) {
bookRepository.findAllIdByLibraryId(libraryId).forEach {

View File

@ -9,6 +9,7 @@ import mu.KotlinLogging
import org.gotson.komga.application.tasks.TaskReceiver
import org.gotson.komga.domain.model.BookSearchWithReadProgress
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.model.ReadStatus
import org.gotson.komga.domain.model.SeriesMetadata
import org.gotson.komga.domain.model.SeriesSearchWithReadProgress
@ -213,7 +214,7 @@ class SeriesController(
}
@PostMapping("{seriesId}/analyze")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
@ResponseStatus(HttpStatus.ACCEPTED)
fun analyze(@PathVariable seriesId: Long) {
bookRepository.findAllIdBySeriesId(seriesId).forEach {
@ -222,7 +223,7 @@ class SeriesController(
}
@PostMapping("{seriesId}/metadata/refresh")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
@ResponseStatus(HttpStatus.ACCEPTED)
fun refreshMetadata(@PathVariable seriesId: Long) {
bookRepository.findAllIdBySeriesId(seriesId).forEach {
@ -231,7 +232,7 @@ class SeriesController(
}
@PatchMapping("{seriesId}/metadata")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
fun updateMetadata(
@PathVariable seriesId: Long,
@Parameter(description = "Metadata fields to update. Set a field to null to unset the metadata. You can omit fields you don't want to update.")

View File

@ -1,12 +1,16 @@
package org.gotson.komga.interfaces.rest
import mu.KotlinLogging
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.model.ROLE_FILE_DOWNLOAD
import org.gotson.komga.domain.model.ROLE_PAGE_STREAMING
import org.gotson.komga.domain.model.UserEmailAlreadyExistsException
import org.gotson.komga.domain.persistence.KomgaUserRepository
import org.gotson.komga.domain.persistence.LibraryRepository
import org.gotson.komga.domain.service.KomgaUserLifecycle
import org.gotson.komga.infrastructure.security.KomgaPrincipal
import org.gotson.komga.interfaces.rest.dto.PasswordUpdateDto
import org.gotson.komga.interfaces.rest.dto.RolesUpdateDto
import org.gotson.komga.interfaces.rest.dto.SharedLibrariesUpdateDto
import org.gotson.komga.interfaces.rest.dto.UserCreationDto
import org.gotson.komga.interfaces.rest.dto.UserDto
@ -58,13 +62,13 @@ class UserController(
}
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
fun getAll(): List<UserWithSharedLibrariesDto> =
userRepository.findAll().map { it.toWithSharedLibrariesDto() }
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
fun addOne(@Valid @RequestBody newUser: UserCreationDto): UserDto =
try {
userLifecycle.createUser(newUser.toDomain()).toDto()
@ -74,7 +78,7 @@ class UserController(
@DeleteMapping("{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@PreAuthorize("hasRole('ADMIN') and #principal.user.id != #id")
@PreAuthorize("hasRole('$ROLE_ADMIN') and #principal.user.id != #id")
fun delete(
@PathVariable id: Long,
@AuthenticationPrincipal principal: KomgaPrincipal
@ -84,9 +88,29 @@ class UserController(
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
@PatchMapping("{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@PreAuthorize("hasRole('$ROLE_ADMIN') and #principal.user.id != #id")
fun updateUserRoles(
@PathVariable id: Long,
@Valid @RequestBody patch: RolesUpdateDto,
@AuthenticationPrincipal principal: KomgaPrincipal
) {
userRepository.findByIdOrNull(id)?.let { user ->
val updatedUser = user.copy(
roleAdmin = patch.roles.contains(ROLE_ADMIN),
roleFileDownload = patch.roles.contains(ROLE_FILE_DOWNLOAD),
rolePageStreaming = patch.roles.contains(ROLE_PAGE_STREAMING)
)
userRepository.save(updatedUser).also {
logger.info { "Updated user roles: $it" }
}
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
@PatchMapping("{id}/shared-libraries")
@ResponseStatus(HttpStatus.NO_CONTENT)
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
fun updateSharesLibraries(
@PathVariable id: Long,
@Valid @RequestBody sharedLibrariesUpdateDto: SharedLibrariesUpdateDto
@ -99,7 +123,9 @@ class UserController(
.map { it.id }
.toSet()
)
userRepository.save(updatedUser)
userRepository.save(updatedUser).also {
logger.info { "Updated user shared libraries: $it" }
}
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
}

View File

@ -1,6 +1,9 @@
package org.gotson.komga.interfaces.rest.dto
import org.gotson.komga.domain.model.KomgaUser
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.model.ROLE_FILE_DOWNLOAD
import org.gotson.komga.domain.model.ROLE_PAGE_STREAMING
import org.gotson.komga.infrastructure.security.KomgaPrincipal
import javax.validation.constraints.Email
import javax.validation.constraints.NotBlank
@ -47,7 +50,13 @@ data class UserCreationDto(
val roles: List<String> = emptyList()
) {
fun toDomain(): KomgaUser =
KomgaUser(email, password, roleAdmin = roles.contains("ADMIN"))
KomgaUser(
email,
password,
roleAdmin = roles.contains(ROLE_ADMIN),
roleFileDownload = roles.contains(ROLE_FILE_DOWNLOAD),
rolePageStreaming = roles.contains(ROLE_PAGE_STREAMING)
)
}
data class PasswordUpdateDto(
@ -58,3 +67,7 @@ data class SharedLibrariesUpdateDto(
val all: Boolean,
val libraryIds: Set<Long>
)
data class RolesUpdateDto(
val roles: List<String>
)

View File

@ -8,6 +8,7 @@ import org.gotson.komga.domain.model.BookPage
import org.gotson.komga.domain.model.BookSearch
import org.gotson.komga.domain.model.KomgaUser
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.model.makeBook
import org.gotson.komga.domain.model.makeLibrary
import org.gotson.komga.domain.model.makeSeries
@ -218,6 +219,41 @@ class BookControllerTest(
}
}
@Nested
inner class RestrictedUserByRole {
@Test
@WithMockCustomUser(roles = [])
fun `given user without page streaming role when getting specific book page then returns unauthorized`() {
makeSeries(name = "series", libraryId = library.id).let { series ->
seriesLifecycle.createSeries(series).let { created ->
val books = listOf(makeBook("1", libraryId = library.id))
seriesLifecycle.addBooks(created, books)
}
}
val book = bookRepository.findAll().first()
mockMvc.get("/api/v1/books/${book.id}/pages/1")
.andExpect { status { isForbidden } }
}
@Test
@WithMockCustomUser(roles = [])
fun `given user without file download role when getting specific book file then returns unauthorized`() {
makeSeries(name = "series", libraryId = library.id).let { series ->
seriesLifecycle.createSeries(series).let { created ->
val books = listOf(makeBook("1", libraryId = library.id))
seriesLifecycle.addBooks(created, books)
}
}
val book = bookRepository.findAll().first()
mockMvc.get("/api/v1/books/${book.id}/file")
.andExpect { status { isForbidden } }
}
}
@Nested
inner class MediaNotReady {
@Test
@ -404,7 +440,7 @@ class BookControllerTest(
}
@Test
@WithMockCustomUser(roles = ["ADMIN"])
@WithMockCustomUser(roles = [ROLE_ADMIN])
fun `given admin user when getting books then full url is available`() {
val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series ->
seriesLifecycle.createSeries(series).also { created ->
@ -550,7 +586,7 @@ class BookControllerTest(
"""{"authors":"[{"name":""}]"}""",
"""{"ageRating":-1}"""
])
@WithMockCustomUser(roles = ["ADMIN"])
@WithMockCustomUser(roles = [ROLE_ADMIN])
fun `given invalid json when updating metadata then raise validation error`(jsonString: String) {
mockMvc.patch("/api/v1/books/1/metadata") {
contentType = MediaType.APPLICATION_JSON
@ -561,7 +597,7 @@ class BookControllerTest(
}
@Test
@WithMockCustomUser(roles = ["ADMIN"])
@WithMockCustomUser(roles = [ROLE_ADMIN])
fun `given valid json when updating metadata then fields are updated`() {
makeSeries(name = "series", libraryId = library.id).let { series ->
seriesLifecycle.createSeries(series).also { created ->
@ -642,7 +678,7 @@ class BookControllerTest(
}
@Test
@WithMockCustomUser(roles = ["ADMIN"])
@WithMockCustomUser(roles = [ROLE_ADMIN])
fun `given json with null fields when updating metadata then fields with null are unset`() {
val testDate = LocalDate.of(2020, 1, 1)
@ -699,7 +735,7 @@ class BookControllerTest(
}
@Test
@WithMockCustomUser(roles = ["ADMIN"])
@WithMockCustomUser(roles = [ROLE_ADMIN])
fun `given json without fields when updating metadata then existing fields are untouched`() {
val testDate = LocalDate.of(2020, 1, 1)

View File

@ -1,5 +1,7 @@
package org.gotson.komga.interfaces.rest
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.model.ROLE_USER
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
@ -36,7 +38,7 @@ class FileSystemControllerTest(
}
@Test
@WithMockUser(roles = ["USER", "ADMIN"])
@WithMockUser(roles = [ROLE_USER, ROLE_ADMIN])
fun `given relative path param when getDirectoryListing then return bad request`() {
mockMvc.post(route) {
contentType = MediaType.APPLICATION_JSON
@ -45,7 +47,7 @@ class FileSystemControllerTest(
}
@Test
@WithMockUser(roles = ["USER", "ADMIN"])
@WithMockUser(roles = [ROLE_USER, ROLE_ADMIN])
fun `given non-existent path param when getDirectoryListing then return bad request`() {
val parent = Files.createTempDirectory(null)
Files.delete(parent)

View File

@ -1,5 +1,7 @@
package org.gotson.komga.interfaces.rest
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.model.ROLE_USER
import org.gotson.komga.domain.model.makeLibrary
import org.gotson.komga.domain.persistence.LibraryRepository
import org.junit.jupiter.api.AfterAll
@ -70,7 +72,7 @@ class LibraryControllerTest(
}
@Test
@WithMockUser(roles = ["USER"])
@WithMockUser(roles = [ROLE_USER])
fun `given user with USER role when addOne then return forbidden`() {
val jsonString = """{"name":"test", "root": "C:\\Temp"}"""
@ -114,7 +116,7 @@ class LibraryControllerTest(
}
@Test
@WithMockCustomUser(roles = ["ADMIN"])
@WithMockCustomUser(roles = [ROLE_ADMIN])
fun `given admin user when getting books then root is available`() {
mockMvc.get(route)
.andExpect {

View File

@ -1,6 +1,9 @@
package org.gotson.komga.interfaces.rest
import org.gotson.komga.domain.model.KomgaUser
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.model.ROLE_FILE_DOWNLOAD
import org.gotson.komga.domain.model.ROLE_PAGE_STREAMING
import org.gotson.komga.infrastructure.security.KomgaPrincipal
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContext
@ -13,7 +16,7 @@ import org.springframework.security.test.context.support.WithSecurityContextFact
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class, setupBefore = TestExecutionEvent.TEST_EXECUTION)
annotation class WithMockCustomUser(
val email: String = "user@example.org",
val roles: Array<String> = [],
val roles: Array<String> = [ROLE_FILE_DOWNLOAD, ROLE_PAGE_STREAMING],
val sharedAllLibraries: Boolean = true,
val sharedLibraries: LongArray = [],
val id: Long = 0
@ -27,7 +30,9 @@ class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory<With
KomgaUser(
email = customUser.email,
password = "",
roleAdmin = customUser.roles.contains("ADMIN"),
roleAdmin = customUser.roles.contains(ROLE_ADMIN),
roleFileDownload = customUser.roles.contains(ROLE_FILE_DOWNLOAD),
rolePageStreaming = customUser.roles.contains(ROLE_PAGE_STREAMING),
sharedAllLibraries = customUser.sharedAllLibraries,
sharedLibrariesIds = customUser.sharedLibraries.toSet(),
id = customUser.id

View File

@ -4,6 +4,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.gotson.komga.domain.model.BookPage
import org.gotson.komga.domain.model.KomgaUser
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.gotson.komga.domain.model.SeriesMetadata
import org.gotson.komga.domain.model.makeBook
import org.gotson.komga.domain.model.makeLibrary
@ -316,7 +317,7 @@ class SeriesControllerTest(
}
@Test
@WithMockCustomUser(roles = ["ADMIN"])
@WithMockCustomUser(roles = [ROLE_ADMIN])
fun `given admin user when getting series then url is available`() {
val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series ->
seriesLifecycle.createSeries(series).also { created ->
@ -366,7 +367,7 @@ class SeriesControllerTest(
"""{"title":""}""",
"""{"titleSort":""}"""
])
@WithMockCustomUser(roles = ["ADMIN"])
@WithMockCustomUser(roles = [ROLE_ADMIN])
fun `given invalid json when updating metadata then raise validation error`(jsonString: String) {
mockMvc.patch("/api/v1/series/1/metadata") {
contentType = MediaType.APPLICATION_JSON
@ -377,7 +378,7 @@ class SeriesControllerTest(
}
@Test
@WithMockCustomUser(roles = ["ADMIN"])
@WithMockCustomUser(roles = [ROLE_ADMIN])
fun `given valid json when updating metadata then fields are updated`() {
val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series ->
seriesLifecycle.createSeries(series).also { created ->

View File

@ -1,5 +1,9 @@
application.version: TESTING
komga:
database-backup:
enabled: false
spring:
datasource:
url: jdbc:h2:mem:testdb