full reborn
This commit is contained in:
18
features/roles/server/build.gradle
Normal file
18
features/roles/server/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
}
|
||||
|
||||
apply from: "$mppJavaProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":postssystem.features.roles.common")
|
||||
api project(":postssystem.features.common.server")
|
||||
api project(":postssystem.features.auth.server")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package dev.inmo.postssystem.features.roles.server
|
||||
|
||||
import dev.inmo.postssystem.features.roles.common.*
|
||||
import dev.inmo.postssystem.features.users.common.User
|
||||
import io.ktor.application.ApplicationCall
|
||||
|
||||
interface RolesChecker<T : UserRole> {
|
||||
val key: String
|
||||
|
||||
suspend operator fun ApplicationCall.invoke(
|
||||
usersRolesStorage: ReadUsersRolesStorage<T>,
|
||||
user: User
|
||||
): Boolean
|
||||
|
||||
companion object {
|
||||
fun <T : UserRole> default(
|
||||
key: String,
|
||||
role: T
|
||||
): RolesChecker<T> = object : RolesChecker<T> {
|
||||
override val key: String
|
||||
get() = key
|
||||
|
||||
override suspend fun ApplicationCall.invoke(
|
||||
usersRolesStorage: ReadUsersRolesStorage<T>,
|
||||
user: User
|
||||
): Boolean = usersRolesStorage.contains(user.id, role)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package dev.inmo.postssystem.features.roles.server
|
||||
|
||||
import dev.inmo.postssystem.features.roles.common.UserRole
|
||||
import dev.inmo.postssystem.features.roles.common.UsersRolesStorage
|
||||
import dev.inmo.postssystem.features.users.common.UserId
|
||||
|
||||
class UsersRolesAggregator(
|
||||
private val otherStorages: List<UsersRolesStorageHolder<*>>
|
||||
) : UsersRolesStorage<UserRole> {
|
||||
private val otherStoragesByClass = otherStorages.associateBy { it.kclass }
|
||||
|
||||
override suspend fun getUsers(userRole: UserRole): List<UserId> {
|
||||
return otherStoragesByClass[userRole::class] ?.getUsers(userRole) ?: emptyList()
|
||||
}
|
||||
|
||||
override suspend fun getRoles(userId: UserId): List<UserRole> = otherStorages.flatMap { it.getRoles(userId) }
|
||||
|
||||
override suspend fun contains(userId: UserId, userRole: UserRole): Boolean {
|
||||
return otherStoragesByClass[userRole::class] ?.contains(userId, userRole) ?: false
|
||||
}
|
||||
|
||||
override suspend fun containsAny(userId: UserId, userRoles: List<UserRole>): Boolean {
|
||||
return userRoles.any {
|
||||
contains(userId, it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun include(
|
||||
userId: UserId,
|
||||
userRole: UserRole
|
||||
): Boolean = otherStoragesByClass[userRole::class] ?.include(userId, userRole) ?: false
|
||||
|
||||
override suspend fun exclude(
|
||||
userId: UserId,
|
||||
userRole: UserRole
|
||||
): Boolean {
|
||||
return otherStoragesByClass[userRole::class] ?.exclude(userId, userRole) ?: false
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package dev.inmo.postssystem.features.roles.server
|
||||
|
||||
import dev.inmo.postssystem.features.auth.common.AuthToken
|
||||
import dev.inmo.postssystem.features.auth.server.principal
|
||||
import dev.inmo.postssystem.features.auth.server.tokens.AuthTokensService
|
||||
import dev.inmo.postssystem.features.common.server.sessions.ApplicationAuthenticationConfigurator
|
||||
import dev.inmo.postssystem.features.roles.common.UserRole
|
||||
import dev.inmo.postssystem.features.roles.common.UsersRolesStorage
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.Authentication
|
||||
import io.ktor.auth.session
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.response.respond
|
||||
|
||||
class UsersRolesAuthenticationConfigurator<T : UserRole>(
|
||||
private val usersRolesStorage: UsersRolesStorage<T>,
|
||||
private val authTokensService: AuthTokensService,
|
||||
private val rolesCheckers: List<RolesChecker<T>>
|
||||
) : ApplicationAuthenticationConfigurator.Element {
|
||||
override fun Authentication.Configuration.invoke() {
|
||||
rolesCheckers.forEach { checker ->
|
||||
session<AuthToken>(checker.key) {
|
||||
validate {
|
||||
val result = authTokensService.getUserPrincipal(it)
|
||||
if (result.isSuccess) {
|
||||
val user = result.getOrThrow().principal()
|
||||
if (checker.run { invoke(usersRolesStorage, user.user) }) {
|
||||
user
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
challenge { call.respond(HttpStatusCode.Unauthorized) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
package dev.inmo.postssystem.features.roles.server
|
||||
|
||||
import dev.inmo.postssystem.features.roles.common.UserRole
|
||||
import dev.inmo.postssystem.features.roles.common.UsersRolesStorage
|
||||
import dev.inmo.postssystem.features.users.common.UserId
|
||||
import dev.inmo.micro_utils.common.*
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
data class UsersRolesStorageHolder<T : UserRole>(
|
||||
val kclass: KClass<T>,
|
||||
val storage: UsersRolesStorage<T>
|
||||
) {
|
||||
private suspend fun <R> doIfRelevant(
|
||||
userRole: UserRole,
|
||||
block: suspend (T) -> R
|
||||
): Optional<R> = if (kclass.isInstance(userRole)) {
|
||||
block(userRole as T).optional
|
||||
} else {
|
||||
Optional.absent()
|
||||
}
|
||||
|
||||
suspend fun getUsers(userRole: UserRole): List<UserId>? = doIfRelevant(userRole) {
|
||||
storage.getUsers(it)
|
||||
}.dataOrNull()
|
||||
|
||||
suspend fun getRoles(userId: UserId): List<UserRole> = storage.getRoles(userId)
|
||||
|
||||
suspend fun contains(userId: UserId, userRole: UserRole): Boolean? = doIfRelevant(userRole) {
|
||||
storage.contains(userId, it)
|
||||
}.dataOrNull()
|
||||
|
||||
suspend fun include(
|
||||
userId: UserId,
|
||||
userRole: UserRole
|
||||
): Boolean? = doIfRelevant(userRole) {
|
||||
storage.include(userId, it)
|
||||
}.dataOrNull()
|
||||
|
||||
suspend fun exclude(userId: UserId, userRole: UserRole): Boolean? = doIfRelevant(userRole) {
|
||||
storage.exclude(userId, it)
|
||||
}.dataOrNull()
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
package dev.inmo.postssystem.features.roles.server
|
||||
|
||||
import dev.inmo.postssystem.features.roles.common.*
|
||||
import dev.inmo.postssystem.features.users.common.UserId
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.authenticate
|
||||
import io.ktor.routing.*
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
class UsersRolesStorageReadServerRoutesConfigurator<T : UserRole>(
|
||||
private val storage: ReadUsersRolesStorage<T>,
|
||||
private val serializer: KSerializer<T>
|
||||
) : ApplicationRoutingConfigurator.Element {
|
||||
private val userRolesSerializer = ListSerializer(serializer)
|
||||
override fun Route.invoke() {
|
||||
authenticate {
|
||||
route(usersRolesRootPathPart) {
|
||||
get(usersRolesGetUsersPathPart) {
|
||||
val userRole = call.decodeUrlQueryValueOrSendError(usersRolesUserRoleQueryParameterName, serializer)
|
||||
?: return@get
|
||||
call.unianswer(
|
||||
UsersIdsSerializer,
|
||||
storage.getUsers(userRole)
|
||||
)
|
||||
}
|
||||
|
||||
get(usersRolesGetRolesPathPart) {
|
||||
val userId =
|
||||
call.decodeUrlQueryValueOrSendError(usersRolesUserIdQueryParameterName, UserId.serializer())
|
||||
?: return@get
|
||||
call.unianswer(
|
||||
userRolesSerializer,
|
||||
storage.getRoles(userId)
|
||||
)
|
||||
}
|
||||
|
||||
get(usersRolesContainsPathPart) {
|
||||
val userId = call.decodeUrlQueryValueOrSendError(
|
||||
usersRolesUserIdQueryParameterName,
|
||||
UserId.serializer()
|
||||
) ?: return@get
|
||||
val userRole = call.decodeUrlQueryValueOrSendError(
|
||||
usersRolesUserRoleQueryParameterName,
|
||||
serializer
|
||||
) ?: return@get
|
||||
call.unianswer(
|
||||
Boolean.serializer(),
|
||||
storage.contains(userId, userRole)
|
||||
)
|
||||
}
|
||||
|
||||
get(usersRolesContainsAnyPathPart) {
|
||||
val userId = call.decodeUrlQueryValueOrSendError(
|
||||
usersRolesUserIdQueryParameterName,
|
||||
UserId.serializer()
|
||||
) ?: return@get
|
||||
val userRoles = call.decodeUrlQueryValueOrSendError(
|
||||
usersRolesUserRoleQueryParameterName,
|
||||
userRolesSerializer
|
||||
) ?: return@get
|
||||
call.unianswer(
|
||||
Boolean.serializer(),
|
||||
storage.containsAny(userId, userRoles)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package dev.inmo.postssystem.features.roles.server
|
||||
|
||||
import dev.inmo.postssystem.features.roles.common.*
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.authenticate
|
||||
import io.ktor.routing.*
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
class UsersRolesStorageWriteServerRoutesConfigurator<T : UserRole>(
|
||||
private val storage: WriteUsersRolesStorage<T>,
|
||||
private val serializer: KSerializer<T>,
|
||||
private val includeAuthKey: String,
|
||||
private val excludeAuthKey: String = includeAuthKey
|
||||
) : ApplicationRoutingConfigurator.Element {
|
||||
override fun Route.invoke() {
|
||||
route(usersRolesRootPathPart) {
|
||||
val wrapperSerializer = UserRolesStorageIncludeExcludeWrapper.serializer(
|
||||
serializer
|
||||
)
|
||||
authenticate(includeAuthKey) {
|
||||
post(usersRolesIncludePathPart) {
|
||||
val wrapper = call.uniload(wrapperSerializer)
|
||||
|
||||
call.unianswer(
|
||||
Boolean.serializer(),
|
||||
storage.include(
|
||||
wrapper.userId,
|
||||
wrapper.userRole
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
authenticate(excludeAuthKey) {
|
||||
post(usersRolesExcludePathPart) {
|
||||
val wrapper = call.uniload(wrapperSerializer)
|
||||
|
||||
call.unianswer(
|
||||
Boolean.serializer(),
|
||||
storage.exclude(
|
||||
wrapper.userId,
|
||||
wrapper.userRole
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user