From 4d27ba991a3061e914b93453f5c209dd65e09820 Mon Sep 17 00:00:00 2001 From: Ziedelth Date: Sat, 10 Feb 2024 15:07:30 +0100 Subject: [PATCH] Change catalog uuid to slug and add roles list for members --- .../controllers/site/SiteController.kt | 9 ++- .../member/MemberToTokenDtoConverter.kt | 3 +- .../kotlin/fr/shikkanime/entities/Member.kt | 8 ++- src/main/kotlin/fr/shikkanime/plugins/HTTP.kt | 20 +++---- .../kotlin/fr/shikkanime/plugins/Routing.kt | 3 +- .../kotlin/fr/shikkanime/plugins/Security.kt | 10 ++-- .../repositories/MemberRepository.kt | 24 +++++++- .../fr/shikkanime/services/MemberService.kt | 6 +- .../kotlin/fr/shikkanime/utils/Constant.kt | 1 - .../fr/shikkanime/utils/EncryptionManager.kt | 2 +- .../db/changelog/2024/02/05-changelog.xml | 56 +++++++++++++++++++ .../db/changelog/db.changelog-master.xml | 1 + src/main/resources/templates/site/catalog.ftl | 17 +++++- .../templates/site/components/episode.ftl | 13 ++++- .../controllers/api/MetricControllerTest.kt | 3 - 15 files changed, 137 insertions(+), 39 deletions(-) create mode 100644 src/main/resources/db/changelog/2024/02/05-changelog.xml diff --git a/src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt b/src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt index 02c973f0..05268a20 100644 --- a/src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt +++ b/src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt @@ -14,7 +14,6 @@ import fr.shikkanime.utils.routes.Response import fr.shikkanime.utils.routes.method.Get import fr.shikkanime.utils.routes.param.PathParam import io.ktor.http.* -import java.util.* @Controller("/") class SiteController { @@ -85,14 +84,14 @@ class SiteController { private fun catalog(): Response { val findAll = simulcastCacheService.findAll()!! val currentSimulcast = findAll.first() - return Response.redirect("/catalog/${currentSimulcast.uuid}") + return Response.redirect("/catalog/${currentSimulcast.season.lowercase()}-${currentSimulcast.year}") } - @Path("catalog/{uuid}") + @Path("catalog/{slug}") @Get - private fun catalogSimulcast(@PathParam("uuid") uuid: UUID): Response { + private fun catalogSimulcast(@PathParam("slug") slug: String): Response { val findAll = simulcastCacheService.findAll()!! - val currentSimulcast = findAll.first { it.uuid == uuid } + val currentSimulcast = findAll.first { "${it.season.lowercase()}-${it.year}" == slug } return Response.template( Link.CATALOG, diff --git a/src/main/kotlin/fr/shikkanime/converters/member/MemberToTokenDtoConverter.kt b/src/main/kotlin/fr/shikkanime/converters/member/MemberToTokenDtoConverter.kt index 9de99590..707694e2 100644 --- a/src/main/kotlin/fr/shikkanime/converters/member/MemberToTokenDtoConverter.kt +++ b/src/main/kotlin/fr/shikkanime/converters/member/MemberToTokenDtoConverter.kt @@ -10,13 +10,14 @@ import java.util.* class MemberToTokenDtoConverter : AbstractConverter() { override fun convert(from: Member): TokenDto { + println(from) val token = JWT.create() .withAudience(Constant.jwtAudience) .withIssuer(Constant.jwtDomain) .withClaim("uuid", from.uuid.toString()) .withClaim("username", from.username) .withClaim("creationDateTime", from.creationDateTime.toString()) - .withClaim("role", from.role.name) + .withClaim("roles", from.roles.map { it.name }) .withExpiresAt(Date(System.currentTimeMillis() + (1 * 60 * 60 * 1000))) .sign(Algorithm.HMAC256(Constant.jwtSecret)) diff --git a/src/main/kotlin/fr/shikkanime/entities/Member.kt b/src/main/kotlin/fr/shikkanime/entities/Member.kt index f8f9907c..1dbea427 100644 --- a/src/main/kotlin/fr/shikkanime/entities/Member.kt +++ b/src/main/kotlin/fr/shikkanime/entities/Member.kt @@ -15,7 +15,11 @@ class Member( val username: String? = null, @Column(nullable = false, name = "encrypted_password") val encryptedPassword: ByteArray? = null, - @Column(nullable = false) + @ElementCollection + @CollectionTable( + name = "member_roles", + joinColumns = [JoinColumn(name = "member_uuid")] + ) @Enumerated(EnumType.STRING) - val role: Role = Role.GUEST, + val roles: MutableSet = mutableSetOf(), ) : ShikkEntity(uuid) \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/plugins/HTTP.kt b/src/main/kotlin/fr/shikkanime/plugins/HTTP.kt index 38cf8be2..ced84551 100644 --- a/src/main/kotlin/fr/shikkanime/plugins/HTTP.kt +++ b/src/main/kotlin/fr/shikkanime/plugins/HTTP.kt @@ -57,17 +57,15 @@ fun Application.configureHTTP() { } install(CachingHeaders) { } - if (Constant.isDev) { - install(SwaggerUI) { - swagger { - swaggerUrl = "api/swagger" - forwardRoot = false - } - info { - title = "${Constant.NAME} API" - version = "1.0" - description = "API for testing and demonstration purposes" - } + install(SwaggerUI) { + swagger { + swaggerUrl = "api/swagger" + forwardRoot = false + } + info { + title = "${Constant.NAME} API" + version = "1.0" + description = "API for testing and demonstration purposes" } } } diff --git a/src/main/kotlin/fr/shikkanime/plugins/Routing.kt b/src/main/kotlin/fr/shikkanime/plugins/Routing.kt index 7c25b3ab..17fe0d55 100644 --- a/src/main/kotlin/fr/shikkanime/plugins/Routing.kt +++ b/src/main/kotlin/fr/shikkanime/plugins/Routing.kt @@ -210,10 +210,11 @@ private suspend fun handleRequest( controller: Any, path: String ) { + val userAgent = call.request.userAgent() val parameters = call.parameters.toMap() val replacedPath = replacePathWithParameters("$prefix$path", parameters) - logger.info("$httpMethod ${call.request.origin.uri} -> $replacedPath") + logger.info("$httpMethod ${call.request.origin.uri} -> $replacedPath${if (userAgent != null) " ($userAgent)" else ""}") try { val response = callMethodWithParameters(method, controller, call, parameters) diff --git a/src/main/kotlin/fr/shikkanime/plugins/Security.kt b/src/main/kotlin/fr/shikkanime/plugins/Security.kt index e3bdf002..c1823028 100644 --- a/src/main/kotlin/fr/shikkanime/plugins/Security.kt +++ b/src/main/kotlin/fr/shikkanime/plugins/Security.kt @@ -31,7 +31,7 @@ fun Application.configureSecurity() { .withClaimPresence("uuid") .withClaimPresence("username") .withClaimPresence("creationDateTime") - .withClaimPresence("role") + .withClaimPresence("roles") .build() authentication { @@ -79,7 +79,7 @@ private fun validationSession( val uuid = UUID.fromString(jwtPrincipal.getClaim("uuid").asString()) val username = jwtPrincipal.getClaim("username").asString() val creationDateTime = jwtPrincipal.getClaim("creationDateTime").asString() - val role = Role.valueOf(jwtPrincipal.getClaim("role").asString()) + val roles = jwtPrincipal.getClaim("roles").asArray(Role::class.java) val member = memberCacheService.find(uuid) ?: return null if (member.username != username) { @@ -87,8 +87,8 @@ private fun validationSession( return null } - if (member.role != role) { - logger.log(Level.SEVERE, "Error while validating session: role mismatch") + if (!member.roles.toTypedArray().contentEquals(roles)) { + logger.log(Level.SEVERE, "Error while validating session: roles mismatch") return null } @@ -97,7 +97,7 @@ private fun validationSession( return null } - if (member.role != Role.ADMIN) { + if (member.roles.none { it == Role.ADMIN }) { logger.log(Level.SEVERE, "Error while validating session: role is not admin") return null } diff --git a/src/main/kotlin/fr/shikkanime/repositories/MemberRepository.kt b/src/main/kotlin/fr/shikkanime/repositories/MemberRepository.kt index 283db771..9d4e5fcf 100644 --- a/src/main/kotlin/fr/shikkanime/repositories/MemberRepository.kt +++ b/src/main/kotlin/fr/shikkanime/repositories/MemberRepository.kt @@ -2,13 +2,30 @@ package fr.shikkanime.repositories import fr.shikkanime.entities.Member import fr.shikkanime.entities.enums.Role +import org.hibernate.Hibernate +import java.util.UUID class MemberRepository : AbstractRepository() { - fun findAllByRole(role: Role): List { + private fun Member.initialize(): Member { + Hibernate.initialize(this.roles) + return this + } + + private fun List.initialize(): List { + this.forEach { member -> member.initialize() } + return this + } + + override fun find(uuid: UUID) = inTransaction { + it.find(getEntityClass(), uuid)?.initialize() + } + + fun findAllByRoles(roles: List): List { return inTransaction { - createReadOnlyQuery(it, "FROM Member WHERE role = :role", getEntityClass()) - .setParameter("role", role) + createReadOnlyQuery(it, "SELECT m FROM Member m JOIN m.roles r WHERE r IN :roles", getEntityClass()) + .setParameter("roles", roles) .resultList + .initialize() } } @@ -19,6 +36,7 @@ class MemberRepository : AbstractRepository() { .setParameter("password", password) .resultList .firstOrNull() + ?.initialize() } } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/services/MemberService.kt b/src/main/kotlin/fr/shikkanime/services/MemberService.kt index cf0f61d9..b9360ccd 100644 --- a/src/main/kotlin/fr/shikkanime/services/MemberService.kt +++ b/src/main/kotlin/fr/shikkanime/services/MemberService.kt @@ -17,17 +17,17 @@ class MemberService : AbstractService() { override fun getRepository() = memberRepository - private fun findAllByRole(role: Role) = memberRepository.findAllByRole(role) + private fun findAllByRoles(roles: List) = memberRepository.findAllByRoles(roles) fun findByUsernameAndPassword(username: String, password: String) = memberRepository.findByUsernameAndPassword(username, EncryptionManager.generate(password)) fun initDefaultAdminUser(): String { - val adminUsers = findAllByRole(Role.ADMIN) + val adminUsers = findAllByRoles(listOf(Role.ADMIN)) check(adminUsers.isEmpty()) { "Admin user already exists" } val password = RandomManager.generateRandomString(32) logger.info("Default admin password: $password") - save(Member(username = "admin", encryptedPassword = EncryptionManager.generate(password), role = Role.ADMIN)) + save(Member(username = "admin", encryptedPassword = EncryptionManager.generate(password), roles = mutableSetOf(Role.ADMIN))) return password } } \ No newline at end of file diff --git a/src/main/kotlin/fr/shikkanime/utils/Constant.kt b/src/main/kotlin/fr/shikkanime/utils/Constant.kt index 54057207..f7a27830 100644 --- a/src/main/kotlin/fr/shikkanime/utils/Constant.kt +++ b/src/main/kotlin/fr/shikkanime/utils/Constant.kt @@ -27,7 +27,6 @@ object Constant { return dataFolder } - var isDev = System.getenv("ENV") == "dev" val abstractSocialNetworks = reflections.getSubTypesOf(AbstractSocialNetwork::class.java).map { injector.getInstance(it) } val utcZoneId: ZoneId = ZoneId.of("UTC") diff --git a/src/main/kotlin/fr/shikkanime/utils/EncryptionManager.kt b/src/main/kotlin/fr/shikkanime/utils/EncryptionManager.kt index ae49108e..f8839d58 100644 --- a/src/main/kotlin/fr/shikkanime/utils/EncryptionManager.kt +++ b/src/main/kotlin/fr/shikkanime/utils/EncryptionManager.kt @@ -5,7 +5,7 @@ import org.bouncycastle.crypto.params.Argon2Parameters import java.nio.charset.StandardCharsets object EncryptionManager { - private val salt = "ShikkAnime".toByteArray(StandardCharsets.UTF_8) + private val salt = Constant.NAME.toByteArray(StandardCharsets.UTF_8) private const val ITERATIONS = 2 private const val MEM_LIMIT = 66536 private const val HASH_LENGTH = 32 diff --git a/src/main/resources/db/changelog/2024/02/05-changelog.xml b/src/main/resources/db/changelog/2024/02/05-changelog.xml new file mode 100644 index 00000000..50e8b62f --- /dev/null +++ b/src/main/resources/db/changelog/2024/02/05-changelog.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT COUNT(*) + FROM member + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-master.xml b/src/main/resources/db/changelog/db.changelog-master.xml index 34e08031..2b7b848e 100644 --- a/src/main/resources/db/changelog/db.changelog-master.xml +++ b/src/main/resources/db/changelog/db.changelog-master.xml @@ -22,4 +22,5 @@ + \ No newline at end of file diff --git a/src/main/resources/templates/site/catalog.ftl b/src/main/resources/templates/site/catalog.ftl index e9c12253..a2ef4403 100644 --- a/src/main/resources/templates/site/catalog.ftl +++ b/src/main/resources/templates/site/catalog.ftl @@ -2,15 +2,28 @@ <#import "components/episode.ftl" as episodeComponent /> <#import "components/anime.ftl" as animeComponent /> +<#function getPrefixSimulcast(season)> + <#switch season> + <#case "WINTER"> + <#return "Hiver"> + <#case "SPRING"> + <#return "Printemps"> + <#case "SUMMER"> + <#return "Été"> + <#case "AUTUMN"> + <#return "Automne"> + + + <@navigation.display>
diff --git a/src/main/resources/templates/site/components/episode.ftl b/src/main/resources/templates/site/components/episode.ftl index 347d79be..0bea972c 100644 --- a/src/main/resources/templates/site/components/episode.ftl +++ b/src/main/resources/templates/site/components/episode.ftl @@ -1,3 +1,14 @@ +<#function getPrefixEpisode(episodeType)> + <#switch episodeType> + <#case "EPISODE"> + <#return "Épisode"> + <#case "FILM"> + <#return "Film"> + <#case "SPECIAL"> + <#return "Spécial"> + + + <#macro display episode>
@@ -16,7 +27,7 @@ ${episode.anime.shortName}

Saison ${episode.season?c} | - Épisode ${episode.number?c}<#if episode.uncensored> non censuré + ${getPrefixEpisode(episode.episodeType)} ${episode.number?c}<#if episode.uncensored> non censuré

<#if episode.langType == 'SUBTITLES'>Sous-titrage<#else>Doublage

diff --git a/src/test/kotlin/fr/shikkanime/controllers/api/MetricControllerTest.kt b/src/test/kotlin/fr/shikkanime/controllers/api/MetricControllerTest.kt index 30aa3644..8bbc35d2 100644 --- a/src/test/kotlin/fr/shikkanime/controllers/api/MetricControllerTest.kt +++ b/src/test/kotlin/fr/shikkanime/controllers/api/MetricControllerTest.kt @@ -1,7 +1,6 @@ package fr.shikkanime.controllers.api import fr.shikkanime.module -import fr.shikkanime.utils.Constant import io.ktor.client.* import io.ktor.client.engine.mock.* import io.ktor.client.request.* @@ -28,8 +27,6 @@ class MetricControllerTest { @Test fun `get metrics authorized`() { - Constant.isDev = true - testApplication { application { module()