Pergunta

I am working on a small application trying to grasp the principles of domain-driven design. If successful, this might be a pilot for a larger project. I'm trying to follow the book "Implementing Domain-Driven Design" (by Vaughn Vernon) and trying to implement a similar, simple discussion forum. I've also checked out the IDDD samples on github. I have some difficulties adopting the Identity and Access to my case. Let me give some background information:

  • I (hopefully) understand the reasoning behind separating the users and permissions logic: it is a supporting domain, and it's a different bounded context.

  • In the core domain, there are no users, just Authors, Moderators, etc. These are created by reaching out to the Identity and Access context using a service and then translating the received User objects to and Moderator.

  • Domain operations are called with a related role as a parameter: e.g.:

    ModeratePost( ..., moderator);

  • The method of the domain object checks if the given Moderator instance is not null (the Moderator instance will be null if the user asked from the Identity and Access context does not have the Moderator role).

  • In one case, it does an additional check before altering a Post:

    if (forum.IsModeratedby(moderator))

My questions are:

  • In the latter case aren't the security concerns blended again into the core domain? Previously the books states "with who can post a subject, or under what conditions that is permitted. A Forum just needs to know that an Author is doing that right now".

  • The role based implementation in the book is fairly straightforward: when a Moderator is the core domain tries to convert the current userId into a Moderator instance or into an Author when it needs that. The service will respond with the appropriate instance or a null if the user does not have the required role. However, I can't see how could I adapt this to a more complex security model; our current project I'm piloting for has a rather complex model with groups, ACLs, etc.

Even with rules that are not much very complex, like: "A post should be edited only by its Owner or an Editor", this approach seems to break down, or at least I don't see the correct way to implement it.

By asking the Identity and Access context for an OwnerOrEditor instance doesn't feel right, and I would end up with more and more security-related classes in the core domain. In addition, I would need to pass not just the userId, but the identifier of the protected resource (the id of the post, forum, etc.) to the security context, which probably should not care about these things (is it correct?)

By pulling the permissions to the core domain and checking them in the methods of the domain objects or in the services, I'd end up at square one: mixing security concerns with the domain.

I've read somewhere (and I tend to agree with it) that these permission related things should not be a part of the core domain, unless security and permissions are the core domain itself. Does a simple rule like the one given above justify making security a part of the core domain?

Foi útil?

Solução

It's sometimes difficult to distinguish between real access control rules and domain invariants borderlining on access control.

Especially, rules that depend on data only available far into the course of a particular piece of domain logic might not easily be extractible out of the domain. Usually, Access Control is called before or after a domain operation is performed but not during.

Vaughn Vernon's assert (forum.IsModeratedBy(moderator)) example probably should have been outside the Domain, but it is not always feasible.

I would need to pass not just the userId, but the identifier of the protected resource (the id of the post, forum, etc.) to the security context, which probably should not care about these things (is it correct?)

If there's a Security BC and you want it to handle that logic, it doesn't have to know what a Forum is in detail but :

  • It could just have knowledge of concepts like "moderated by" and grant or deny access rights accordingly.
  • You could have adapter logic that subscribes to Core Domain events and translates them to simple (resource, authorizedUsers) key value pairs for the Security BC to store and use.

Outras dicas

Authentication and Authorisation is a bad example for DDD.

Neither of these things are part of a Domain unless your company creates security products.

The Business or domain requirement is, or should be, "I require role based authentication"

You then check the role before calling a domain function.

Where you have complex requirements such as 'I can edit my own posts but not others' ensure that your domain separates the edit function out into EditOwnPost() and EditOthersPost() so that you have a simple function to role mapping

You can also separate the functionality into Domain Objects, such as Poster.EditPost() and Moderator.EditPost() this is a more OOP approach, although your choice may depend on whether your method is in a Domain Service or a Domain Object.

However you choose to separate the code the Role mapping will occur outside of the Domain. so for example if you have a webapi controller:

PostController : ApiController
{
    [Authorize(Roles = "User")]
    public void EditOwnPost(string postId, string newContent)
    {
        this.postDomainService.EditOwnPost(postId, string newContent);
    }

    [Authorize(Roles = "Moderator")]
    public void EditOtherPost(string postId, string newContent)
    {
        this.postDomainService.EditOtherPost(postId, string newContent);
    }
}

As you can see although the role mapping is done on the hosting layer, the complex logic of what constitutes editing your own or an others post is part of the domain.

The domain recognises the difference of the actions, but the security requirement is simply that "functionality can be limited by roles".

This is perhaps clearer with the domain objects separation, but essentially you are checking the method which constructs the object instead of the method which calls the service method. Your requirement, if you still want to voice it as part of the domain would become 'only moderators can construct the moderator object'

I have run into the same questioning and am posting the answer I have come up with because it might give some additional vision on the topic, even though the discussion took place a while ago (still second link on the topic in Google).

I think there are two different kinds of access controls at play:

  • Role validation in terms of authentication scheme
  • Action validation in business terms

Knowing if a particular user is a Moderator belongs to the Identity & Access bounded context. That's because it has to do with the rights the user has - its category.

But

Knowing if a moderator can edit a post belongs to the business domain, and should be out of the Identity & Access bounded context. A good way to see that is that the condition is expressed without referring to the targeted user: it is a business rule that a moderator can or cannot edit another moderator's post for example. It should not pollute the Identity & Access BC.

It becomes especially clear when you want to apply this reasoning to scopes in an OAuth setting. Now in addition to auth rules, you also have to authorize actions based on whether specific scopes are included or not in the token request. Where do you put that?

I'd say you put that in the business BC. That's because the scopes semantics are known to the business domain. Having a specific role and knowing what it allows to do are two different matters. First part is in the I&A BC, second part is in the business domain.

And it feels right: if suddenly you want to forbid topic edition by a moderator, you should have to modify the forum BC, not the I&A BC - nothing has changed to roles definition really, it's just their semantics in a specific BC that changed.

That's why I think it is the best solution to have the check if (forum.IsModeratedby(moderator)) in the business BC. I guess I disagree with the accepted answer on that matter.

If many rules like that exist for a specific business BC, it seems to me that the best way is to bundle them in a subdomain in the business BC. Business rules like that can benefit from being embodied as an object to call (like a Strategy pattern).

Licenciado em: CC-BY-SA com atribuição
scroll top