0

There is a web app that uses the following stack on the server: akka-persistence/service-layer/akka-http (for REST)

The question is : how can I - in the most elegant, most dry way - make sure that only those users can execute a function in the service layer who are authorized to do so (under the given input parameters).

So for example let's take the simple example:

getEntity(userID:UserID, ref:EntityID):Entity = ???

how should I modifiy getEntity such that only those users are allowed to execute it where the userID of the caller is the same as the userID in the parameters?

What is the most elegant, composable, dry way to do this in general ?

Using custom monads?

Using continuation monads?

Using akka-http style directives?

Using implicits?

Using Free-Monads?

Using Arrows?

Using Kleiesly ?

I cannot really imagine.

jhegedus
  • 20,244
  • 16
  • 99
  • 167
  • 1
    What's the simplest thing that could possibly work? – Seth Tisue Aug 06 '17 at 13:42
  • 1
    It will depend on the size of the application, the complexity of the permission system and your taste as well. There is no one size fits all solution to that problem. – michaJlS Aug 06 '17 at 14:57
  • 1
    My solution has been an Authorization service, which manually replicates the interfaces of my service methods, plus a Credentials parameter, which encapsulates all the data needed to make an authorization decision. The external APIs call the service methods through the Authorizing facade. I'm sure this could be made a bit more boilerplate-free, but we don't have all that many services. – acjay Aug 08 '17 at 18:07
  • @acjay I see, so where is the Authorization decision made or actually how do you ensure that there is no way to execute anything (by accident) unauthorized in the service layer ? So basically that there is no path to bypass the service layer (and carry out any side effecting or even data read operation) without authorization ? I was thinking that having some kind of monad might be a general way to do this because that sort of taints the data types that have been authorized. so somehow the type system would help in ensuring that nothing unauthorized happens. – jhegedus Aug 09 '17 at 01:22
  • @jhegedus There's no such protection in my codebase. But yeah, I imagine you could use some sort of effects system to enlist the compiler in helping you out there. It wouldn't eliminate human error, but it might raise the bar. Maybe a simpler approach would be to only pass the Authorization service to your external API services. Note that if your permissions are dynamic, a type-level approach can't really help you. At best, it can help enforce that you've wired everything up correctly. – acjay Aug 10 '17 at 03:45
  • @acjay So you have 3 layers : service layer <=uses= authorization facade <=uses= rest layer . Right ? – jhegedus Aug 10 '17 at 05:59
  • @jhegedus Yep! In reality there are more layers than that, but if you smashed all the others into "service layer", you can think of it that way. – acjay Aug 10 '17 at 11:36

1 Answers1

1

How I would do it would be to use an implicit context:

getEntity(userID:UserID, ref:EntityID)(implicit c: Context): Entity = ???

and then whatever is in the context is up to you, i.e.

trait Context {
   def canExecute(userId: UserID): Boolean
}

You may want to break this out so you have roles, permissions, etc. Also, if you are using a type parameter then you can use a context bound and make it a bit cleaner:

trait Context[A] { ... }
def getEntity[A: Context](userID:UserID, ref:EntityID): Entity = ???

Implicit parameters are great for being able to pass through contextual information like "current security context" or "correlation id" that is not core to the domain logic but has to be involved all the way down the stack.

Will Sargent
  • 4,346
  • 1
  • 31
  • 53