Pergunta

I'm working on implementing my first clean architecture and CQRS application, I've stumbled across a bit of logic that's leaving me a bit stumped. I'm working on integrating authorization and authentication into my application, but I'm having some trouble trying to figure out how to architect a portion of my application that will handle command validation. My system is multitenant and currently shares a database across all of my clients, and I need to implement some sort of system that will enable me to perform all of my verification in a way that is straightforward and not too tightly coupled.

In my system, I have a implementations for both Users and Clients. Users can belong to any number clients. These users also have varying levels of permissions granted to them (but this is managed by my roles systems and mostly irrelevant here). My WebAPI is designed in such a way that I limit authorization based on a fixed number of Roles and Claims. These largely remain static, so I don't need much flexibility here.

My main source of headache is determining "User-Client" interaction permissions. Namely, I want to determine if a user has access to client that they're attempted to update records for. One approach would be to add UserId to all of my commands and to individually check the permissions during each command and query. This seems tedious and prone to issues.

Another approach I considered was to define an interface or base class that merely added the client and user Ids to any object in which they were required, but this had the adverse affect of exposing those implementations to my WebAPIs (via swagger and the UserId/ClientId being a portion of the Request/Command object).

One final approach would be to make my underlying commands still implement those interfaces, but have my controller contain minimal logic to map an API request into one of my command objects. Again, this would be tedious and would start leaking my logic into my controllers.

Overall, it's as if I need some additional structure in my application in which I can pipe any object containing a ClientId into so that I can keep the Authentication and Authorization logic out of my core app. However, I'm mostly stumped, and I'm looking for ways that I can simplify my application by minimizing the overhead of adding client-dependent commands and queries.

If it's of any relevance, some of the core tools and technologies I'm leveraging are:

  • .Net Core 2.1
  • Mediatr
  • Entity Framework

And my core application implements commands in a similar fashion to:

public class CreateProductCommand : IRequest
{
    public int ClientId { get;set; }
    public int ParentProductId { get; set; }
    public string ProductName { get; set; }
    // other creation specific props here
}

public class CreateProductCommandHandler : IHandler<CreateProductCommand>
{
    public async Task<Unit> Handle(CreateProductCommand command)
    {
        // check parent permissions, make sure parent product
        // belongs to client who is entered. 
        // -----
        // rest of logic to save

        // -- Ideally the User-Client check would happen before
        // -- the command is ever sent to the handler, so that
        // -- only client-specific logic and permissions are checked.
        // -- As long as the user can edit the specific client, anything
        // -- that happens to the client is determined by standard business 
        // -- and domain logic.
    } 
}
Foi útil?

Solução

Looks to me like your issue is:

I have a thread acting on behalf of a user. I don't wish to pollute my nice clean business logic with authorisation details. But my data store needs authorisation details to enforce control of the data.

From your description it sounds pretty critical that User A does/does not have access to Client B. That sounds very much like a Business Concern. So the first answer is its pretty obvious: Your business logic should care about which user is doing what. Passing the user details through the appropriate business logic directly is the clean way to handle this. This gives many positives: its clear, its clean, its easily tested, and the API is not coupled to the Data Store.

If need be there will be a point in your Business Logic where you can translate from talking about a User, to specifically talking about Authorised to do X. Also there is no reason why this User information could not be made more anemic, or enriched in different parts of your Business Logic.

Pragmatically, if the thread only serves one User at a time, use Thread Local storage and shove a reference to the user there. Later in your data store (aka wherever you need it) access that reference. This is a dreadful solution. It enforces direct coupling between API and Data Store (the recipient location), the Business Concern is not in the Business Logic, and you have a non-intuitive, indirect argument, that may not be set or cleaned up properly affecting future calls on the thread. In short what you make back from tedium, you will pay back over and over again in bugs and change resistance.


Added extra answer

Possible Solutions

  1. Attribute with some Aspect-Orientated programming.
  2. an Abstract Class/interface with some Aspect-Orientated programming.
  3. A Generic class that wraps a lambda/interface.
  4. a Meta Program that constructs an Abstract class for derivation.
  5. a Meta Program that constructs a tailored class which wraps a lambda/interface.

Solutions 1 and 2 uses your languages aspect orientation (C# attributes) to detect the "authorisation required" functions, and intercept calls to them. It will throw an exception on unauthorised, but permit the call if authorised.

class command
{
    public command(User user);

    public User user {get; }

    [AuthorisedFor("xyz")]
    public void action(object a);

    [AuthorisedFor("xyz")]
    public void action(User user, object a);
}

Solution 1 requires you to directly decorate the authorisation required functions, and provide an properties/arguments for user information.

Solution 2 allows you to pre-specify the authorisation required for the standard functions, and provide the properties/arguments for user information.

Solution 3 is a decorator and captures through its constructor the knowledge about authority required, the permit and deny functions. The only issue is that the Command interface will either force you to use Object arguments, a Generic Type and a limit on arguments, or require copies per downstream command interface.

class command<T>
{
    public command(User user, Authority[] required, Action<T> permit, Action<T> deny);

    public void action(T arguments);
}
class command2
{
    public command(Authority[] required, SomeInterface permit, SomeInterface2 deny);

    public void action();
}

Solutions 4 and 5 are just not easily achieved in C#. You would essential need to write code that JIT's a new Base Class and derivations, or decorator classes for the various interfaces. I only include them for completeness.

The run-time flexible options are 3 or 5. The other solutions 1, 2 or 4 are more reasonable for compile time.

User and Authorisation Passing

Orthogonal to which solution you pick for handling permit/deny, those solutions will need access to the user/authorisations. These could be stored in various forms of storage:

  • global
  • thread local
  • object property
  • function argument

Picking Global or thread local is going to give you the implicit user passing you desire, it will however complicate testing and be a source of non-intuitive bugs.

Picking an Object property or a function argument is going to require that you pass the user/authorisation information through your commands. It will make the information requirement explicit, simplify testing, and reduce the capacity for non-intuitive bugs. It will require more typing.

My preference is for explicitly passing the user/authorisations down. However there may be a reason that makes the implicit Global/Thread local option the better choice.

Outras dicas

Create IPermissionValidator that you inject from command's constructor using dependency injection. When you need to check permissions for CreateProduct you call _permissionValidator.ValidateCreateProduct(command.ParentProductId);. If user doesn't have permission it will throw exception PermissionValidationException that your web api will catch and return correct HTTP status code. If you need to way to check if user has permission and not throw, add _permissionValidator.TryValidateCreateProduct(command.ParentProductId); that returns bool true if it's valid. IPermissionValidator implementation can use dependency injection to get current user and validate accordingly.

This also makes your commands easily testable, because you can implement own IPermissionValidator for tests. You should tests all cases of validation in your commands.

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