# Access control with path-dependent types

There are several approaches to an access control in web applications, and one of important questions that often arises is "where to put these access control checks"? The typical answers are:

  • In a model. It looks like a safe choice, because if something is forbidden in a model, then view and controller methods are unable to bypass the security checks. However, these checks happen too late, and you may find yourself wasting time in heavy computations before you actually realize that you don't have a permission to save a result. You will also have to find a way to pass user information to model methods unless your framework does it for you.
  • In a controller. You can check access rights as early as needed, and a user information is available without extra effort. On the other side, it is difficult to verify that you have all checks in place, especially with complex ACLs.
  • Everywhere. In addition to advantages and disadvantages of the previous two options, you will have to keep model and controller access rules consistent.

An interesting option would be to have some tool verifying if model and controller checks are consistent. For example, we can try to use a Scala compiler for this. To be more precise, we want the following controller code to compile:

def copy(src: ResourceId, dst: ResourceId)(implicit ctx: SecurityContext) {
  implicit val token = canRead(src) and canWrite(dst)
  dao.write(dst, dao.read(src))
}

But if we forgot some security check, it should be a compilation error:

def copy(src: ResourceId, dst: ResourceId)(implicit ctx: SecurityContext) {
  implicit val token = canRead(src)
  // won't compile because we didn't check if we can write to dst
  dao.write(dst, dao.read(src))
}

Moreover, we want some role-based access control features:

def copy(src: ResourceId, dst: ResourceId)(implicit ctx: SecurityContext) {
  implicit val token = isAdministrator
  // will compile because administrators are gods
  dao.write(dst, dao.read(src))
}

The main idea is to use a path-dependent types to tie granted permissions to a resource identifier. First of all, let's define some types:

trait Feature trait Permission extends Feature
trait Administrator extends Role
trait ResourceActions {
  trait CanRead extends Permission trait CanWrite extends Permission
}

We will have a single resource type:

case class ResourceId(id: String) extends ResourceActions
case class Resource(content: String)

We also need a type accumulating all permissions we are granted:

class Token[+T <: Feature] {
  def and[S <: Feature](t: Token[S]) = new Token[T with S]
}

def grant[T <: Feature]: Token[T] = new Token[T]
def forbid[T <: Feature]: Token[T] = throw new NotAuthorized

For example, if we have tested that we can read resource a and write to resource b we will get a token of type Token[a.CanRead with b.CanWrite]. So, we can specify the security constraints in model methods:

class ResourceDao(storage: mutable.Map[ResourceId, Resource]) {

  def read(id: ResourceId)(implicit token: Token[id.CanRead]) = { storage.get(id) }

  def write(id: ResourceId, data: Option[Resource])(implicit token: Token[id.CanWrite]) {
    data match {
      case Some(v) => storage(id) = v
      case None => storage -= id
    }
  }

  def resources(implicit token: Token\[Administrator\]) = {
    storage.keys.toSet
  }

}

Now anyone who wants to work with our resources must have an appropriate security token. Let's implement a simple access policy that will be used in a controller:

class SecurityContext(val user: Option[String])

// we have a single administrator
def isAdministrator(implicit ctx: SecurityContext): Token[Administrator] = {
  ctx.user match {
    case Some("administrator") => grant
    case _ => forbid
  }
}

// everyone can read
def canRead(id: ResourceId)(implicit ctx: SecurityContext): Token[id.CanRead] = {
  grant
}

// only administrators and resource owners can write
// (owner is a user with a name equal to the resource id)
def canWrite(id: ResourceId)(implicit ctx: SecurityContext): Token[id.CanRead with id.CanWrite] = {
  ctx.user match {
    case Some("administrator") => grant
    case Some(user) if (user == id.id) => grant
    case _ => forbid
  }
}

// administrator has read and write permissions for all resources
implicit def administratorCanEverything[R <: ResourceActions](implicit t: Token[Administrator]) = {
  new Token[R#CanRead with R#CanWrite]
}

Finally, we can implement a controller itself:

class SimpleService(dao: ResourceDao) {

  def get(id: ResourceId)(implicit ctx: SecurityContext) = {
    implicit val token = canRead(id)
    dao.read(id).get.content
  }

  def set(id: ResourceId, content: String)(implicit ctx: SecurityContext) {
    implicit val token = canWrite(id)
    dao.write(id, Some(new Resource(content)))
  }

  def copy(src: ResourceId, dst: ResourceId)(implicit ctx: SecurityContext) {
    implicit val token = canRead(src) and canWrite(dst)
    dao.write(dst, dao.read(src))
  }

  def clear(implicit ctx: SecurityContext) {
    implicit val token = isAdministrator
    dao.resources foreach { dao.write(_, None) }
  }

}

The full source code is available on GitHub (opens new window). There is also a small web application based on the Unfiltered framework that uses a similar approach (here (opens new window)).