2

I have class that represents user authorization (scala code):

case class Authorization(
                     userId: UUID,
                     permissions: Seq[String],                     
                     userRoles: Seq[String],
                     companyId: UUID
                   ) {

  private val policy = new Policy(permissions)

  def isAllowed(path: String, action: String): Boolean = policy.isAllowed(path, action)

  def isSuperuser: Boolean = userRoles.contains(SuperUser)

} 

This class is used in many places in application to check whether user has specific permission for specific entities, e.g.:

authorization.isAllowed(s"some/entity/path/$id", "read")

To improve usability and avoid direct string manipulation I wrapped such methods in more specific ones:

def canViewCompany(companyId:UUID)=authorization.isAllowed(s"company/$companyId","read")

def canViewUser(userId:UUID)=authorization.isAllowed(s"user/$userId","read")

def canEditTask(id:UUID)=authorization.isAllowed(....)

....

and so on, up to 20 methods. Should Authorization contain such methods like canViewCompany() ?
Is it responsibility of this class to know about each specific entities check?

UPDATED: So eventually questions are:

  1. Is it a good idea to create wrappers like canViewCompany(companyId:UUID) in order to improve usability of the class?
  2. If so, should these methods beplaced in Authorization itself or to be extracted to some service like: PermissionService.canViewCompany(id,authorization)=authorization.isAllowed()?
GoodPerson
  • 141
  • 2
  • 6
  • Isn't `path` outside the domain? Are these permissions related to the domain or APIs? Should the domain be validating these or should they be part of the application layer (for example, in Application Services)? – Subhash Sep 12 '19 at 04:26

4 Answers4

3

In DDD, only methods related to entity should be located in entity class like create and update. This sort of methods just works on the entity properties and fields. As far as I know, Authorization is a service not entity, so it would be better to define it as a service that other entities like User and Policy should be used in it to do some operations like isAllowed. Finally, you should incorporate methods are related to Authorization into this service.

Ali Soltani
  • 9,589
  • 5
  • 30
  • 55
  • AFAIN entity can contain any business logic, but if some logic is overlapped by multiple entities or just is out of entity scope, then should go into domain service. At first look there is unclear what actually `Authorization` should be. – GoodPerson Sep 19 '19 at 17:05
1

Authorization does not seem like an entity here - it's rather a property of a User. The fact that its key is UserID can hint at that.

Much clearer is something like

user.IsAllowed(action, path)

or

user.canView(company)

How that is implemented is up to the entity - you might have an authorization service, etc.

Mathieson
  • 1,698
  • 2
  • 17
  • 19
1

That Authorization service you mention seems to be like a toolbox with actually poor domain meaning. You are trying to use some kind of object, creating some kind of abstraction, incapsulating some kind of behavior in a "general purpose" service.

As soon as you have new needs in your domain, this object, as you say, will grow. So basically you are forced to modify your code all the time, breaking the open-closed principle of SOLID.

I suggest you to move that responsibility of authorize a given user for a given action, inside the use case, so you will have multiple use cases in your domain that will never change, each one of them knowing their own logic for authorization.

The only reason to modify those use cases is if the authorization logic changes for that specific use case, but that's fine, because if logic changes, so your domain does.

Moving this responsibility inside the use case will help you creating more scalable and extensible code.

Of course, depends on the case, but the rule of thumbs is to push logic inside your domain the more you can, and that doesn't mean to create a lot of domain services to store that logic, because that way you will create an anemic domain model, and you don't want that.

Moreover, I suggest you to apply the TELL DON'T ASK principle to keep your logic straightforward.

Good luck!

rastafermo
  • 408
  • 1
  • 5
  • 13
0

My approach would be to load a UserPrincipal (paraphrasing .net) with all the permissions and have a method that would check if certain permission is in the Seq[Permissions]. Permission could be an object with the path and action. Using this approach, you don't need to create an army of methods. Just one method that is going to validate the path and the action. Also, it allows you to pass this object across your application without worrying about checking the database in each operation.

In a nutshell, you should have a service that takes care of the Authentication/Authorization. This service creates a UserPrincipal with all the data you need to check for access.

Does it make sense?

Pepito Fernandez
  • 2,352
  • 6
  • 32
  • 47
  • But seems I already have such one- it is `isAllowed(path: String, action: String)` method that check if certain permission is in the permissions set, but delegates this logic to inner `Policy` class. – GoodPerson Sep 19 '19 at 17:09