Skip to content

Instantly share code, notes, and snippets.

@bseib
Last active May 22, 2022 20:20
Show Gist options
  • Save bseib/fbc3121ac91328162478ce6ee431b8e7 to your computer and use it in GitHub Desktop.
Save bseib/fbc3121ac91328162478ce6ee431b8e7 to your computer and use it in GitHub Desktop.
Succinct and/or composable/nestable role based access control
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())
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)))
}
}
@bseib
Copy link
Author

bseib commented May 22, 2022

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 your accessRequirement variable and it will collapse all the redundant and no-op rules into the simplest rule possible. Then call toString() to see the human readable format.

This unit test is a good demonstration of this:

            val rule1 = allOfAccessRequirements(
                requireRole(Role.AAA),
                alwaysAllow()
            )
            assertEquals("AllOf[AAA, alwaysAllow]", rule1.toString())
            assertEquals("require[AAA]", rule1.reduce().toString())

Have a look at the other unit tests for more demonstrations of reduce().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment