вторник, 15 июня 2010 г.

Type-safe authorization

While experimenting with Scala and implementing a simple web application i decided to see if i can design type-safe authorization interfaces. I like static typing, if you can do it right (without too much boilerplate) it makes code more transparent and easy to maintain.

Simple authorization system (like the one in Apache Shiro) works like this:
  • user has a set of permissions
  • permissions can imply other permissions
  • different actions can require different combinations of permissions

So we need to express somehow that a method requires a set of permissions and we need to express that certain permissions imply other permissions.

After trying different language constructs (like type parameters, variance etc) and asking for help at scala-user mailing list, i came to the following solution:

object Permissions {
  trait UserPermissions
  trait AnyUserPermissions extends UserPermissions
  trait RegisteredUserPermissions extends AnyUserPermissions
  trait AdminPermissions extends RegisteredUserPermissions

  trait DomainAPermissions extends RegisteredUserPermissions
  trait DomainBPermissions extends RegisteredUserPermissions
  implicit def adm2doma(x: AdminPermissions): DomainAPermissions = new DomainAPermissions{}
  implicit def adm2domb(x: AdminPermissions): DomainBPermissions = new DomainBPermissions{}
}

object Test {
  import Permissions._

  def sampleRegisteredUserFunction[T <% RegisteredUserPermissions](p: T) = println("registered user")
  def sampleCombinedPermissionsFunction[T <% DomainAPermissions <% DomainBPermissions](p: T) = println("combined")

  def main(args: Array[String]) {
    val anyUserPermissions = new UserPermissions{}
    val combinedPermissions = new DomainAPermissions with DomainBPermissions
    val adminPermissions: AdminPermissions = new AdminPermissions{}

    sampleRegisteredUserFunction(combinedPermissions)
    sampleCombinedPermissionsFunction(combinedPermissions)

    sampleRegisteredUserFunction(adminPermissions)
    sampleCombinedPermissionsFunction(adminPermissions)
    sampleRegisteredUserFunction(anyUserPermissions) // compile error - as expected
  }
}
The idea is quite obvious: permissions are traits that you can inherit and mix when implementing your own permissions. If inheritance is not enough (for example, we want AdminPermissions to imply any other permissions including those added by users of our library) we can use implicit conversions.

To require certain sets of permissions in a method we declare it using view bounded type parameters (T <% DomainAPermissions <% DomainBPermissions means that T should be implicitly convertable to both DomainAPermissions and DomainBPermissions).

This way you can mark some parts of your code to require statically checked permissions. I think it's better than explicit runtime checks or annotations with AOP.

Комментариев нет:

Отправить комментарий