JonPSmith / AuthPermissions.AspNetCore

This library provides extra authorization and multi-tenant features to an ASP.NET Core application.
https://www.thereformedprogrammer.net/finally-a-library-that-improves-role-authorization-in-asp-net-core/
MIT License
764 stars 155 forks source link

User access in multiple tenants #91

Closed cwbrandsdal closed 10 months ago

cwbrandsdal commented 10 months ago

Is there a way to give a user access to multiple tenants in different parts of the structure? Example a user needs access to south and west, but not north and east. I guess this is hard because that would cause the user to need multiple DataAccess entries and also multiple DataKey?

JonPSmith commented 10 months ago

Hi @cwbrandsdal,

Louis Miles asked a similar question to your requirements and you can read my reply here.

If you are creating a multi-tenant using the AuthP library, then you add some extra code to use a "multiple tenants" key instead of the single DataKey that AuthP uses. Here are the extra steps you need to do:

  1. Create a claim using the RegisterAddClaimToUser method which creates the "multiple tenants" key when the user logs in.
  2. To inject this "multiple tenants" key you need to create a new interface / class similar to the AuthP's IGetDataKeyFromUser interface, but provides your "multiple tenants" key.
  3. Register your "multiple tenants" interface / class to the DI in your Program / startup code.
  4. Your tenant DbContext constructor you will put in your "multiple tenants" key interface instead of the AuthP's IGetDataKeyFromUser
  5. In the tenant DbContext's OnModelCreating method you will add the ...HasQueryFilter(p => UserKey.Contains(p.GroupKey); to each tenant entity class.

NOTE: If you need the Admin access tenant data, then you have to do some more work. You can add code to make it work, but its a bit complex so I wouldn't add this unless your really need it.

JonPSmith commented 10 months ago

PS. You also need to build some new admin code to create a many-to-many link between the AuthUser and the Tenant entity classes used by AuthP. That shouldn't be too complex.

cwbrandsdal commented 10 months ago

Thank you so much! I was thinking something like this myself. The only thing that makes this a bit hard to work with is that with the new HasQueryFilter using Contains it really needs to have every single child. Do you know if there is a way to use the StartsWith on multiple values? It seems not to work from my testing because the DataKey/DataKeys are not populated before the HasQueryFilter is set up. I guess this makes it really hard. Do you have any suggestions on how I could do this? All help is appreciated. Thank you so much for a GREAT package! :-)

JonPSmith commented 10 months ago

I'm not sure I fully understand your question, but one part stood out:

... the DataKey/DataKeys are not populated before the HasQueryFilter is set up.

The limitation of this approach is a user with many keys, e.g. south and west, cannot create a new entry because you need a single key (e.g. south) to set the DataKey on the entity classes in the tenant database. I was assumed the data was read, but looks at sounds you want read AND write which adds complications. Here are a two approaches:

  1. Have an admin user for each group, e.g. SouthAdmin user, with a single GroupKey, e.g. south, who's job is to add more data. Simple to implement but only good for a multi-tenant where normal users only need to read the data. Only change from the "five steps" described above is you must stop users with multiple GroupKeys from adding data.
  2. When a user wants to add new data, then a dropdown list of the GroupKeys that the user has is shown and the user has to select the correct GroupKey from the dropdown before they can create a new entry. More complex than the first approach, but makes any user to add new data. This adds a extra step to the "five steps" by adding the selected dropdown GroupKey into a "CreateKey" properly in the tenant DbContext, and when new data is written to the database the DataKey on every new entity class is set to the value in the "CreateKey" properly.

NOTE: I didn't explain how the DataKey is set in the "five steps" - see this section in one of my articles.

I hope I have answered your question.