Last active
May 22, 2022 20:20
-
-
Save bseib/fbc3121ac91328162478ce6ee431b8e7 to your computer and use it in GitHub Desktop.
Succinct and/or composable/nestable role based access control
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed class AccessRequirement<R : Enum<R>> { | |
fun isSatisfiedByRoles(callerRoles: Set<R>): Boolean { | |
return when (this) { | |
is AnyOfRoles -> (callerRoles intersect this.anyOfRoles).isNotEmpty() | |
is AllOfRoles -> callerRoles.containsAll(this.allOfRoles) | |
is AnyOfAccessRequirements -> this.anyOf.any { it.isSatisfiedByRoles(callerRoles) } | |
is AllOfAccessRequirements -> this.allOf.all { it.isSatisfiedByRoles(callerRoles) } | |
} | |
} | |
} | |
data class AnyOfAccessRequirements<R : Enum<R>>( | |
val anyOf: Set<AccessRequirement<R>>, | |
) : AccessRequirement<R>() | |
data class AllOfAccessRequirements<R : Enum<R>>( | |
val allOf: Set<AccessRequirement<R>>, | |
) : AccessRequirement<R>() | |
data class AnyOfRoles<R : Enum<R>>( | |
val anyOfRoles: Set<R>, | |
) : AccessRequirement<R>() | |
data class AllOfRoles<R : Enum<R>>( | |
val allOfRoles: Set<R>, | |
) : AccessRequirement<R>() | |
fun <R : Enum<R>> anyOfAccessRequirements(vararg reqs: AccessRequirement<R>) = AnyOfAccessRequirements(reqs.toSet()) | |
fun <R : Enum<R>> allOfAccessRequirements(vararg reqs: AccessRequirement<R>) = AllOfAccessRequirements(reqs.toSet()) | |
fun <R : Enum<R>> anyOfRoles(vararg roles: R) = AnyOfRoles(roles.toSet()) | |
fun <R : Enum<R>> allOfRoles(vararg roles: R) = AllOfRoles(roles.toSet()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.jupiter.api.Test | |
import kotlin.test.assertEquals | |
class TestAccessRequirement { | |
enum class Role { | |
AAA, | |
BBB, | |
CCC, | |
DDD | |
} | |
@Test | |
fun `satifies any of roles`() { | |
val rule = anyOfRoles(Role.AAA, Role.CCC) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.CCC))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.DDD))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.BBB))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.CCC))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.CCC))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.DDD))) | |
} | |
@Test | |
fun `satifies all of roles`() { | |
val rule = allOfRoles(Role.AAA, Role.CCC) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.AAA))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.CCC))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.DDD))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.BBB))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.CCC))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.CCC))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.DDD))) | |
} | |
@Test | |
fun `satifies compound any rule`() { | |
val rule = anyOfAccessRequirements( | |
allOfRoles(Role.AAA, Role.CCC), | |
anyOfRoles(Role.DDD) | |
) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.AAA))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.CCC))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.DDD))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.BBB))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.CCC))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.CCC))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.DDD))) | |
} | |
@Test | |
fun `satifies compound all rule`() { | |
val rule = allOfAccessRequirements( | |
allOfRoles(Role.AAA), | |
anyOfRoles(Role.CCC, Role.DDD) | |
) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.AAA))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.CCC))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.DDD))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.BBB))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.CCC))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.DDD))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.CCC))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.DDD))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.CCC, Role.DDD))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.CCC, Role.DDD))) | |
} | |
@Test | |
fun `satifies compound-compound any all rule`() { | |
val rule = anyOfAccessRequirements( | |
anyOfRoles(Role.BBB), | |
allOfAccessRequirements( | |
allOfRoles(Role.AAA), | |
anyOfRoles(Role.CCC, Role.DDD) | |
) | |
) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.AAA))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.BBB))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.CCC))) | |
assertEquals(false, rule.isSatisfiedByRoles(setOf(Role.DDD))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.BBB))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.CCC))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.DDD))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.CCC))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.DDD))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.AAA, Role.CCC, Role.DDD))) | |
assertEquals(true, rule.isSatisfiedByRoles(setOf(Role.BBB, Role.CCC, Role.DDD))) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The first revision of this gist is still valid, and more compact.
This revision works exactly the same, but makes the access rules human readable when they are nested. Just call
reduce()
on youraccessRequirement
variable and it will collapse all the redundant and no-op rules into the simplest rule possible. Then calltoString()
to see the human readable format.This unit test is a good demonstration of this:
Have a look at the other unit tests for more demonstrations of
reduce()
.