abpframework / abp

Open-source web application framework for ASP.NET Core! Offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET. Provides the fundamental infrastructure, cross-cutting-concern implementations, startup templates, application modules, UI themes, tooling and documentation.
https://abp.io
GNU Lesser General Public License v3.0
12.81k stars 3.42k forks source link

Resource based authorization integration #236

Open hikalkan opened 6 years ago

hikalkan commented 6 years ago

https://docs.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?tabs=aspnetcore2x#operational-requirements

See also https://github.com/aspnetboilerplate/aspnetboilerplate/issues/3231

gentledepp commented 4 years ago

We implemented our resource-based authorization system using this approach AFCAS

It creates 3 graphs in the db: 1) users and groups (groups can contain groups) 2) operations and suboperations 3) resources and sub-resources

While in their approach, they show how to

filter all db records (resource)

  1. by any users that have access
  2. and have a specific permission
  3. for that record (resource) or any of its parent resources

this turned out to be an overkill. When querying the records, we usually only want to know if any user has access. However, the approach has its pros and maybe you want to look into it a bit.

hikalkan commented 3 years ago

Thanks @gentledepp

I have some additional notes on that:

Our purpose to have entity based permissions. Then we will have a few use cases;

  1. Check if a user (or role or OU) has a permission (like edit, delete or a custom permission) on an entity.
  2. Get a list of users (roles/OUs) has a specific permission on an entity
  3. Get a list of entities that a user (role/OU) has a specific permissin

For example, for the file management module, we want to use this feature. A user should see the list of files/folders she has access. Also, we should see the list of users/OUs/roles allowed for an entity to arrange the permissions. It should be compatible to the Microsoft's resource-based authorization API. We should think about tiered / SPA approach. We should think about performance... etc.

gentledepp commented 3 years ago

I see! Well, the AFCAS system supports all features, but when thinking about performance, you do have to be careful. For example, if using

  1. a graph of principals (not necessary, if groups cannot be member of groups)
  2. and a graph of operations (not necessary either, if you always only have 1 parent operation with child-operations like I am used to is in aspnetboilerplate)
  3. and a graph of resources (folders, subfolders) the query involved can get slow quickly.

Especially, because the most generalized approach of AFCAS involves having just one table for all resources. If you have hundreds of thousands of resources with sub-resources, storing the complete transitive closure in the database may not be possible.

I will go through the Microsoft's resource-based authorization API link above once more and try to understand it better.

gentledepp commented 3 years ago

Ok so I went through the docs now. So the ASP.NET Core 3.1 policy based authorization system requires you to provide the

Simple approach for C(R)UD

The simplest approach would be to

  1. allow us to create policies for entities
  2. let the Create/Update/Delete methods of abps repository check whether the current user has the required permissions. (This could also be done in e.g. DbContext.SaveChanges())

public class FolderAuthorizationHandler : AuthorizationHandler<CanViewFolderRequirement, Document> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CanViewFolderRequirementrequirement, Folder resource) { if (context.User.Identity?.Name == resource.Creator) { context.Succeed(requirement); }

    return Task.CompletedTask;
}

}

Alternatively, this handler could query the database for additional permissions.

This approach, however is not very effective, especially if you need to check the permissions on many folders (thousands) where checking in-memory is not feasible anymore.

This would, however suffice for Create/Update/Delete scenarios in the most cases.
We very rarely have the necessity for a query that's like "get me all the folders that the user can delete/update". Normally, you will show all folders and e.g. hide the edit/delete icons from those folders that the user cannot delete/edit.

# How to do the **View** side
Now the question is rather, how implement the View side...
In my ideal world, I would be able to specify an expression `Expression<Func<Folder,bool>> authorizationFilter` that will be applied to any query (or sub-query)

However, unlike model-level filters (e.g. softdelete) these could not be defined globally for all users. As a matter of fact, such filters may require joins and can become quite complex and messy if they need to cover *every* case.

Therefore, it may be be more interesting to define such expressions per user or per role.
The approach would then be
1. Abp sets up the current session with the logged-in user and caches all the authorizationFilters per entity that apply to that user
2. whenever a query is run, it injects the additional expression as a "Where" clause.

Now, the obvious question is *how* to technically do this, but this is currently out of the scope. I merely wanted to start a discussion here ;-)
Also, I need to add that I am not sure how this could work for MongoDB and the like.

This approach would have the **advantage**
- that entities the user does not have Read access to will automatically be filtered
It has the **disadvantages**
- that those filter expressions may result in suboptimal EF Core queries
- that you only get the items you are allowed to see. As the entity is never loaded into memory, we basically do not know if it just does not exist (returning a 404 NotFound) or if it *does* exist, but the user just has no access to it (returning a 403 Forbidden)

However, the latter downside could be overcome: As we specified an `Expression<Func<Folder,bool>> authorizationFilter`, one could disable these filters, load **all** the folders into memory. 
Then, the following code could decide whether a 404 or 403 is appropriate:

var folder = ...//from somewhere

if(folder is null) return NotFound();

var f = authorizationFilter.Compile();

if(!f.Invoke(folder)) return Forbidden();

return f;



What are your thoughts on this?
Am I on the right track or do you have completely opposite ideas?
michaelsudnik commented 3 years ago

We created our own implementation built on top of the already available organization units as described here and it works really well for us.

MongoDB in particular works well for us, as we are able to extend any restricted access entities to include a collection of organization unit IDs, which we are then able to efficiently filter on. We have some nice reusable interfaces and filter logic which makes it easy to restrict access to any entity type and modify the relevant repository with minimal code.

For the UI we updated the relevant create and edit dialogs of relevant entitiy types to include an Organization Units tab (same as the one shown when editing a user)

To grant a user access to the relevant entities (associated with an OU) we add claims to users (or roles) which specify an OU id. We then use the claims to apply filters where necessary within the repositories. We have several claim types, to include/exclude access to a specific OU, or to also include/exclude child OUs.

The existing roles and permissions logic is used in the usual way, and this resource approach just extends it.

Of course this could all be done without using the existing OU functionality at all.

hikalkan commented 3 years ago

Thanks @michaelsudnik for your explanations 👍

murat-yuceer commented 3 years ago

Can we define condition for permissions when you finish implementation. You can referance from xaf https://demos.devexpress.com/XAF/BlazorDemo/PermissionPolicyRole_DetailView/ac55c28c-139d-4d8a-91d5-3d6fa322851e

image

I was started to implemnt for us abp project..

hikalkan commented 3 years ago

Thanks @murat-yuceer for sharing this.

gdirikgil commented 3 years ago

Hi @hikalkan , Is there a plan for this feature in near future?

yellow-dragon-cloud commented 3 years ago

I used this feature with XAF framework. XAF provides row and column based authorization. Each permission can be conditional as well. It's very powerful. But performance is another story! Performance is the hardest (and most crucial) part to implement this feature! I think the structure should be as simple as possible.

custodiodev commented 3 years ago

Hi everyone.

I think casbin is a good option to implement this feature.

It's simple, open-source, flexible (RBAC, RBAC MULTI TENANT, ABAC) and have a good performance.

https://github.com/casbin/Casbin.NET

MrChuJiu commented 1 year ago

I think it's a good choice to use 'Graphql' to implement this kind of license repair

recruitans commented 1 year ago

Merhaba @hikalkan / @ismcagdas , Abp projesinde bana şimdi tam bu lazım oldu. Bu 5 yıl önce açılmış bir isssue olduğundan sormak isterim: Resource based yetkilendirme konusunu abp çözdü mü? Çözdü ise bana doküman ve/veya örnek iletebilir misiniz. Yardım için teşekkürler.

Fyi @huseyinalan

ismcagdas commented 1 year ago

@recruitans it is not implemented yet.

recruitans commented 1 year ago

@recruitans it is not implemented yet. Teşekkürler @ismcagdas . Herhangi bir plan tarih var mı ?

ismcagdas commented 1 year ago

This is not planned yet, so there is no date at the moment for this feature.