135 lines
3.7 KiB
Kotlin
135 lines
3.7 KiB
Kotlin
package fr.dcproject.common.security
|
|
|
|
/** Responses of AccessControl */
|
|
enum class AccessDecision {
|
|
GRANTED,
|
|
DENIED;
|
|
|
|
/**
|
|
* Convert decision to boolean
|
|
*/
|
|
fun toBoolean(): Boolean = when (this) {
|
|
GRANTED -> true
|
|
DENIED -> false
|
|
}
|
|
}
|
|
|
|
abstract class AccessControl {
|
|
/**
|
|
* A Shortcut for return a GrantedResponse
|
|
*/
|
|
protected fun granted(message: String? = null, code: String? = null): GrantedResponse = GrantedResponse(this, message, code)
|
|
/**
|
|
* A Shortcut for return a DeniedResponse
|
|
*/
|
|
protected fun denied(message: String, code: String): DeniedResponse = DeniedResponse(this, message, code)
|
|
|
|
/**
|
|
* Check all responses and return DENIED if one is DENIED
|
|
*
|
|
* If the list of responses is empty, return GRANTED
|
|
*/
|
|
private fun AccessResponses.getOneResponse(): AccessResponse = this.firstOrNull { it.decision == AccessDecision.DENIED } ?: granted()
|
|
|
|
/**
|
|
* An helper to convert a list of subject into one response
|
|
*/
|
|
protected fun <S : List<T>, T> canAll(items: S, action: (T) -> AccessResponse): AccessResponse = items
|
|
.map { action(it) }
|
|
.getOneResponse()
|
|
}
|
|
|
|
/**
|
|
* Throw an Exception if AccessControl return a DENIED response
|
|
*/
|
|
fun <T : AccessControl> T.assert(action: T.() -> AccessResponse) {
|
|
action().assert()
|
|
}
|
|
|
|
/**
|
|
* Check all responses and return DENIED if one is DENIED
|
|
*
|
|
* If the list of responses is empty, return GRANTED
|
|
*/
|
|
fun AccessResponses.getOneResponse(): AccessResponse = this.firstOrNull { it.decision == AccessDecision.DENIED } ?: GrantedResponse(first().accessControl)
|
|
|
|
/**
|
|
* Throw an Exception if one response is DENIED
|
|
*/
|
|
fun AccessResponses.assert() = this.getOneResponse().assert()
|
|
|
|
class AccessDeniedException(private val accessResponses: AccessResponses) : Throwable(accessResponses.first().message) {
|
|
constructor(accessResponse: AccessResponse) : this(listOf(accessResponse))
|
|
|
|
/**
|
|
* Get first response
|
|
*/
|
|
fun first(): AccessResponse = accessResponses.first()
|
|
|
|
/**
|
|
* Check if the error code is present into the responses
|
|
*/
|
|
fun hasErrorCode(code: String): Boolean = accessResponses
|
|
.filter { it.decision == AccessDecision.DENIED }
|
|
.any { it.code == code }
|
|
|
|
/**
|
|
* Find and return the response than match with the error code
|
|
*/
|
|
fun getErrorCode(code: String): AccessResponse? = accessResponses
|
|
.firstOrNull { it.decision == AccessDecision.DENIED && it.code == code }
|
|
|
|
/**
|
|
* Get a list of messages of all responses
|
|
*/
|
|
fun getMessages(): List<String> = accessResponses
|
|
.mapNotNull { it.message }
|
|
|
|
/**
|
|
* Get the first message
|
|
*/
|
|
fun getFirstMessage(): String? = accessResponses
|
|
.first()
|
|
.message
|
|
}
|
|
|
|
/**
|
|
* The response that all AccessControl method return
|
|
* @see GrantedResponse
|
|
* @see DeniedResponse
|
|
*/
|
|
sealed class AccessResponse(
|
|
val decision: AccessDecision,
|
|
val accessControl: AccessControl,
|
|
val message: String?,
|
|
val code: String?
|
|
) {
|
|
/**
|
|
* Convert response as boolean
|
|
*/
|
|
fun toBoolean(): Boolean = decision.toBoolean()
|
|
|
|
/**
|
|
* Throw Exception if response if DENIED
|
|
*/
|
|
fun assert() {
|
|
if (this.decision == AccessDecision.DENIED) {
|
|
throw AccessDeniedException(this)
|
|
}
|
|
}
|
|
}
|
|
|
|
class GrantedResponse(
|
|
accessControl: AccessControl,
|
|
message: String? = null,
|
|
code: String? = null
|
|
) : AccessResponse(AccessDecision.GRANTED, accessControl, message, code)
|
|
|
|
class DeniedResponse(
|
|
accessControl: AccessControl,
|
|
message: String,
|
|
code: String
|
|
) : AccessResponse(AccessDecision.DENIED, accessControl, message, code)
|
|
|
|
typealias AccessResponses = List<AccessResponse>
|