Together with my application's domain logic I am trying to outline the security model. I am stuck with a requirement that prevents me from considering security just a cross-cutting concern over my domain logic. Here follows my situation.
A user in my system can potentially be allowed to create a certain kind of objects, say, 'filters'. I introduce a permission called 'CREATE_FILTER', and a user is either allowed to create filters or not, depending on whether the admin assigned such a permission to this user, or not. Ok.
Now consider a more complex requirement when the number of filters a user can create is limited. So, e.g. the admin should be able to set max number of filters any user is allowed to create, or even more complex, to assign max numbers individually to users, say value of 3 to User1, 5 to User2 and so on. So, the security system, in order to authorize filter creation to a user, is not sufficient to check whether a user has such a permission assigned, but has to analyze the domain model in a complex way in order to look how many filters there are already created by the user to make the decision. To make things more complex, we can imagine that the max limit will depend on the amount of money user has on their account, or something.
I want to conceptually separate (at least in my mind), whether such a complicated security logic purely pertains to security (which will of course depend on the domain model) or is this already a full-fledged part of the domain logic itself? Does it make sense to keep a 'permission' concept, when assigning/removing permissions does not help much (since it's domain state on which depends authorization decision rather than assigned permissions)? Would it be a way to go, say, to have a complicated permission concept which not simply allows an action by a mere fact of its existence but would rather involve some complex decision making logic?
Here's one way you could handle this ...
On one side you have a security model (might be a bounded context in ddd speak) that is solving the problem of assigning permissions to subjects (users), maybe indirectly through the use of roles. I would envision upper boundaries (max numbers) to be an attribute associated with the assigned permission.
There's also a query part to this model. Yet, it can only answer "simple" questions:
- Has this user permission to create filters?
- How many filters can this user create?
Some would even say this query part is a separate model altogether.
On the other end you have your application's model which is largely "security" free apart from these pesky requirements along the lines of "user John Doe can only create 3 filter". As an aside, it's doubtful we're still speaking of a "user" at this point, but rather of a person acting in a certain role in this particular use case. Anyway, back to how we could keep this somewhat separate. Suppose we have a somewhat layered approach and we have an application service with an authorization service in front. The responsibility of the authorization service is to answer the question "is this user allowed to perform this operation? yes or no?" and stop processing if the answer is no. Here's a very naive version of that (C#).
public class FilterAuthorizationServices :
Handles<CreateFilter>
{
public FilterAuthorizationServices(FilterRepository filterRepository) { ... }
public void Authorize(Subject subject, CreateFilter message)
{
if(!subject.HasPermissionTo("CreateFilter"))
{
throw new NotAuthorizedException("...");
}
if(filterRepository.CountFiltersCreatedBy(subject.Id) >
subject.GetTheMaximumNumberOfFiltersAllowedToCreate())
{
throw new NotAuthorizedException("...");
}
}
}
Notice how the application service is not even mentioned here. It can concentrate on invoking the actual domain logic. Yet, the authorization service is using both the query part of the above model (embodied by the Subject) and the model of the application (embodied by the FilterRepository) to fulfill the authorization request. Nothing wrong with doing it this way.
You could even go a step further and ditch the need for the application's model if that model could somehow provide the "current number of created filters" to the security model. But that might be a bridge too far for you, since that would lead down the path of evaluating dynamic expressions (which wouldn't necessarily be a bad place to be in). If you want to go there, may I suggest you create a mini DSL to define the required expressions and associated code to parse and evaluate them.
If you'd like brokered authorization you could look at something like XACML (https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=xacml) though you'll have to overcome your fear of XML first ;-)