val userAdmin = User("admin","admin")
val userTutor = User("tutor", "tutor")
val userList = listOf(userAdmin, userTutor)
val roleAdmin = Role("admin")
val roleUser = Role("user")
val roleList = listOf(roleAdmin, roleUser)
val userRoles = mapOf(
userAdmin to setOf(roleAdmin, roleUser),
userTutor to setOf(roleUser)
)
class UserPrincipal(val user: User): Principal
open class AuthException(
val _message: String
): Exception(_message){
suspend fun handler(call: ApplicationCall){
call.respond(HttpStatusCode.Unauthorized, _message)
}
}
object PrincipalError: AuthException("Missing principal")
object AccessDenied: AuthException("Access is denied")
install(StatusPages) {
exception<AuthException> { call, cause ->
cause.handler(call)
}
}
class AuthorizationConfig(
var getRole: (User) -> Set<Role> = { emptySet() } )
class Authorization(internal var config: AuthorizationConfig) {
companion object : BaseApplicationPlugin
<Application, AuthorizationConfig, Authorization> {
override val key: AttributeKey<Authorization> =
AttributeKey("AuthorizationHolder")
override fun install(
pipeline: Application,
configure: AuthorizationConfig.() -> Unit) =
Authorization(AuthorizationConfig().apply(configure))
}
}
class RouteAuthorizationConfig {
var allowedRoles: () -> Set<Role> = { emptySet() }
}
val RouteAuthorization
: RouteScopedPlugin<RouteAuthorizationConfig> =
createRouteScopedPlugin(
"RouteAuthorization",
::RouteAuthorizationConfig
) { ... }
val holderConfig = application.plugin(Authorization).config
val allowedRoles = pluginConfig.allowedRoles
val getRole = holderConfig.getRole
on(AuthenticationChecked) {
val principal = it.authentication
.principal<UserPrincipal>()?: throw PrincipalError
if (allowedRoles()
.intersect(getRole(principal.user))
.isEmpty())
throw AccessDenied
}
fun Route.authorization(
roles: () -> Set<Role>,
build: Route.() -> Unit
): Route {
val name = roles().joinToString { it.name }
val authenticatedRoute =
createChild(AuthorizationRouteSelector(name))
authenticatedRoute.install(RouteAuthorization) {
this.allowedRoles = roles
}
authenticatedRoute.build()
return authenticatedRoute
}
class AuthorizationRouteSelector(val name: String) :
RouteSelector() {
override fun evaluate(
context: RoutingResolveContext,
segmentIndex: Int): RouteSelectorEvaluation {
return RouteSelectorEvaluation.Transparent
}
override fun toString(): String = "(authorize $name )"
}
fun Route.authorization(
roles: Set<Role>,
build: Route.() -> Unit
): Route = authorization(
{ roles },
build
)
install(Authentication) {
jwt("auth-jwt") {
realm = AuthConfig.myRealm
verifier(
JWT
.require(Algorithm.HMAC256(AuthConfig.secret))
.withAudience(AuthConfig.audience)
.withIssuer(AuthConfig.issuer)
.build()
)
validate { credential ->
userList.find {
it.username == credential
.payload.getClaim("username").asString()
}?.let {
UserPrincipal(it)
}
}
challenge { defaultScheme, realm ->
call.respond(
HttpStatusCode.Unauthorized,
"Token is not valid or has expired")
}
install(Authorization) {
getRole = { user ->
val user = userList.find { it.username == user.username }
userRoles.getOrDefault(user, emptySet())
}
}
post(Config.loginPath) {
val user = call.receive<User>()
val localUser = userList.find { it.username == user.username }
if (localUser?.password != user.password)
return@post call.respondText(...)
val token = JWT.create()
.withAudience(audience)
...
.sign(Algorithm.HMAC256(secret))
call.respond(hashMapOf("token" to token))
}
authenticate("auth-jwt") {
authorization(setOf(roleAdmin)) {
get("ByStartName/{startName}") {
...
enum class ApiPoint {
GetAll, GetById, GetByIds, Post, Put, Delete;
companion object {
val all = setOf(GetAll, GetById, GetByIds,
Post, Put, Delete)
val read = setOf(GetAll, GetById, GetByIds)
val write = setOf(Post, Put, Delete)
}
}
inline fun <reified T : Any> Route.repoRoutes(
repo: Repo<T>,
pointRoles: List<
Pair<Set<ApiPoint>, () -> Set<Role>>
> = emptyList()
) {
val getAll: Route.() -> Route = {
get {
val elemList: List<Item<T>> = repo.read()
if (elemList.isEmpty()) {
call.respondText("No element found",
status = HttpStatusCode.NotFound)
} else {
val elemJson = Json
.encodeToString(listItemSerializer, elemList)
call.respond(elemJson)
}
}
}
val points: (point: ApiPoint) -> Route.() -> Route = {
when (it) {
ApiPoint.GetAll -> getAll
ApiPoint.GetById -> getById
ApiPoint.GetByIds -> getByIds
ApiPoint.Post -> post
ApiPoint.Put -> put
ApiPoint.Delete -> delete
}
}
authenticate("auth-jwt") {
pointRoles.forEach {
authorization(it.second) {
it.first.forEach { apiPoint ->
points(apiPoint)()
}
}
}
}
repoRoutes(
lessonsRepo,
listOf(
ApiPoint.read to { roleList.toSet()},
ApiPoint.write to { setOf(roleAdmin) }
)
)