문제

I read about DDD and Access Control, and I found some contradiction between the following two opinions:

  • "security concerns should be handled outside the domain"
  • "access control requirements are domain specific"

I am looking for a best practice about this. So where should I put the access control logic by domain driven design, and how should I implement it?

(To be more specific by DDD + CQRS + ES.)

I think it should be somewhere near to the business logic, for example a user story could be something like this:

The user can edit his profile by sending a user name, a list of hobbies, cv, etc...

Based on the user story we implement the domain model and the services, for example:

UserService
    editProfile(EditUserProfileCommand command)
        User user = userRepository.getOneById(command.id)
        user.changeName(command.name)
        user.changeHobbies(command.hobbies)
        user.changeCV(command.cv)

UserRepository
    User getOneById(id)
        
User
    changeName(String name)
    changeHobbies(String[] hobbies)
    changeCV(String cv)
    

This is okay, but where is the HIS profile part of the story?

This is obviously attribute based access control, because we should write a rule something like this:

deny all, but if subject.id = resource.owner.id then grant access

But where should we enforce this rule, and how should we implement it?

도움이 되었습니까?

해결책

So where should I put the access control logic?

According to this: https://softwareengineering.stackexchange.com/a/71883/65755 the policy enforcement point should be right before the call of the UserService.editProfile().

I came to the same conclusion: it cannot be in the UI because by multiple UIs we would have code repetition. It should be before the creation of domain events, because they indicated that we have already done something in the system. So we can restrict the access to domain objects or to services which use those domain objects. By CQRS we don't necessary have domain objects by the read model, just services, so we have to restrict access to the services if we want a general solution. We could put the access decisions at the beginning of every service operation, but that would be grant all, deny x security anti pattern.

How should I implement it?

This depends on which access control model fits to the domain, so it depends on the user story. By an access decision we usually send an access request and wait a permission in return. The access request usually has the following parts: subject, resource, operation, environment. So the subject requires permission to perform an operation on the resource in an environment. First we identify the subject, then we authenticate it, and after that comes the authorization, where we check whether the access request fits to our access policy. Every access control model works in a similar way. Ofc. they can lack of some of these steps, but that does not matter...

I created a short list of access control models. I put the rules, policies into annotations, but normally we should store them in a database probably in XACML format if we want to have a well maintainable system...

  • By identity based access control (IBAC) we have an identity - permission storage (access control list, capability list, access control matrix). So for example by an access control list, we store the list of the users or groups whose can have permissions.

    UserService
        @AccessControlList[inf3rno]
        editProfile(EditUserProfileCommand command)
    
  • By lattice based access control (LBAC) the subject has a clearance level, the resource has a required clearance level, and we check which level is higher...

    @posseses[level=5]
    inf3rno
    
    UserService
        @requires(level>=3)
        editProfile(EditUserProfileCommand command)
    
  • By role based access control (RBAC) we define subject roles and we grant permissions to subjects whose act the actual role.

    @roles[admin]
    inf3rno
    
    UserService
        @requires(role=admin)
        editProfile(EditUserProfileCommand command)
    
  • By attribute based access control (ABAC) we define subject, resource and environment attributes and we write our policies based on them.

    @attributes[roles=[admin]]
    inf3rno
    
    UserService
        @policy(subject.role=admin or resource.owner.id = subject.id)
        editProfile(EditUserProfileCommand command)
        @attribute(owner)
        Subject getOwner(EditUserProfileCommand command)
    
  • By policy based access control (PBAC) we don't assign our policies to anything else, they are standalone.

    @attributes[roles=[admin]]
    inf3rno
    
    UserService
        editProfile(EditUserProfileCommand command)
        deleteProfile(DeleteUserProfileCommand command)
        @attribute(owner)
        Subject getOwner(EditUserProfileCommand command)
    
    @permission(UserService.editProfile, UserService.deleteProfile)
    @criteria(subject.role=admin or resource.owner.id = subject.id)
    WriteUserServicePolicy
    
  • By risk-adaptive access control (RAdAC) we base our decision on the relative risk profile of the subject and the risk level of the operation. This cannot be described with rules I think. I am unsure of the implementation, maybe this is what stackoverflow uses by its point system.

  • By authorization based access control (ZBAC) we don't do identification and authentication, instead we assign permissions to identification factors. For example if somebody sends a token, then she can have access to a service. Everything else is similar to the previous solutions. For example with ABAC:

    @attributes[roles=[editor]]
    token:2683fraicfv8a2zuisbkcaac
    
    ArticleService
        @policy(subject.role=editor)
        editArticle(EditArticleCommand command)
    

    So everybody who knows the 2683fraicfv8a2zuisbkcaac token can use the service.

and so on...

There are many other models, and the best fit always depends on the needs of your customer.

So to summarize

- "security concerns should be handled outside the domain"
- "access control requirements are domain specific"

both can be right, because security is not part of the domain model, but its implementation depends on the domain model and the application logic.

edit after 2 years 2016-09-05

Since I answered my own question as a DDD newbie, I have read Implementing Domain-Driven Design from Vaughn Vernon. It was an interesting book in the topic. Here is a quote from it:

This constitutes a new Bounded Context - the Identity and Access Context - and will be used by other Bounded Contexts through standard DDD integration techniques. To the consuming contexts the Identity and Access Context is a Generic Subdomain. The product will be named IdOvation.

So according to Vernon probably the best solution to move the access control to a generic subdomain.

다른 팁

But where should we enforce this rule, and how should we implement it?

Consider use case, username can only be changed by Administrator, thus Authorization should be passed to each entities and domain services behaviors/methods (including factory method).

In following example, Application service is a service that reflects use case of the application/API. It dependent on domain's repositories and authorization service. Injects all required dependencies into Application Service(s).

Based on your example, we can modify it as following:

UserRepository interface
    User getOneById(id)

AuthorizationService interface
    ActiveUser getActiveUser(String token)

ActiveUser interface
    Role getRole()
    Id getId()

ApplicationService

    //dependencies
    UserRepository userRepository
    AuthorizationService authorizationService
    ...

    editProfile(RequestContext context, EditUserProfileCommand command)
        activeUser = authorizationService.getActiveUser(context.getAccessToken)
        User user = userRepository.getOneById(command.id)
        user.changeName(activeUser, command.name)
        user.changeHobbies(activeUser, command.hobbies)
        user.changeCV(activeUser, command.cv)
        userRepository.save(user)

===
User
    changeName(ActiveUser activeUser, String name)
    changeHobbies(ActiveUser activeUser, String[] hobbies)
    changeCV(ActiveUser activeUser, String cv)

//example how to handle in entity behavior
changeName(ActiveUser activeUser, String newName)
    if activeUser.getRole() != UserRole.ADMINISTRATOR
        throw UnauthorizedException()
    this.name = newName

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top