Managing Access at the Single Entity Level in Applications based on abp.io framework

Explore the "Entity Access Policy" (EAP) for advanced data management and security in abp.io framework applications.

In today’s rapidly evolving technology landscape, ensuring secure and effective data access management has become a key challenge for many companies. In one of our recent projects, our team was tasked with designing a system that would allow for precise management of access to various application data. Our solution, which we named “Entity Access Policy” (EAP), was created in response to the specific needs of our client who wanted the ability to determine which data individual users could access.

Genesis of “Entity Access Policy”

Our journey began with an analysis of the client’s requirements, who needed a system that could define and manage data access within the application at the level of individual entities. The client wanted access to be regulated for individual users, which prompted us to create a more granular permission management system.

Application of Teams in Access Management

In response to the client’s needs for more detailed access management, we introduced the concept of Teams. Teams allow users to be grouped within specific roles and permissions, facilitating effective management of access to various business object levels within the system. For example, in a project management system, Teams can be assigned at different levels of the project hierarchy—from the entire project, through task collections, to individual tasks.

Architecture and Implementation

A key component of the “Entity Access Policy” is the IRestrictedEntity interface, which every entity must implement. This interface is linked to the EntityAccessPolicyGroup, which in turn contains a list of EntityAccessPolicyAssignments. Each assignment refers to a specific access policy defined in EntityAccessPolicy, where we store the identifiers of users authorised to access the entity (AllowedUsersIds).

public interface IRestrictedEntity
{
    EntityAccessPolicyGroup EntityAccessPolicyGroup { get; }
    Guid EntityAccessPolicyGroupId { get; }
}

public class EntityAccessPolicyGroup
{
    public List<EntityAccessPolicyAssignment> AccessPolicies { get; }
    public DateTime CreationTime { get; protected set; }
}

public class EntityAccessPolicyAssignment
{
    public EntityAccessPolicy AccessPolicy { get; protected set; }
    public Guid AccessPolicyId { get; protected set; }
}

public class EntityAccessPolicy
{
    public List<Guid> AllowedUsersIds { get; protected set; }
}

Why Multiple Access Policies?

The diversity of business scenarios requires flexibility in access management. For example, a task may belong to more than one task collection, necessitating the assignment of multiple access policies to the same entity, as user permissions may arise from access to two different Teams. This secures against accidental data exposure to unauthorised persons while ensuring that all relevant teams have appropriate access.

Data Filters and Security

A key aspect of security in our solution is data filtering at the SQL query level. We use Entity Framework Core and its data filtering mechanisms to filter out entities at the database query stage to which the user does not have access. Considering the issue of filter application, we decided that for operations that serve only to read data, such as HTTP GET requests, filters will always be applied. In our implementation, to filter entities based on user access rights, we’ve employed Entity Framework Core’s Global Query Filters, encapsulated within the ABP framework’s AbpDbContext. This integration allows us to seamlessly apply these filters across all database queries in a unified manner.

protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
{
    var expression = base.CreateFilterExpression<TEntity>();
    if (typeof(IRestrictedEntity).IsAssignableFrom(typeof(TEntity)))
    {
        Expression<Func<TEntity, bool>> restrictedEntityFilter =
            e => !RestrictedEntityFilterEnabled || !CurrentUser.Id.HasValue || ((IRestrictedEntity)e).EntityAccessPolicyGroup.AccessPolicies.Any(x => x.AccessPolicy.AllowedUsersIds.Contains(CurrentUser.Id ?? Guid.Empty));
        expression = expression == null
            ? restrictedEntityFilter
            : QueryFilterExpressionHelper.CombineExpressions(expression, restrictedEntityFilter);
    }
    return expression;
}

For further details on how ABP handles these filters and integrates with Entity Framework Core, you can refer to the ABP documentation on Data Filtering. This resource explains the underlying concepts and provides examples of how to implement data filtering effectively within the ABP framework. However, for operations that modify data, filters are not applied by default; instead, we check permissions to perform the operation as discussed below. This decision stems from concerns that applying filters during data modification could lead to incomplete or incorrect operations. For example, if a task in the project leads to the update of project statistics, restricting access to certain tasks could result in incorrect calculation of these statistics.

Permissions and Authorization

In implementing the “Entity Access Policy” system, we also had to ensure that a user could only perform actions on entities for which they have the appropriate permissions. In addition to standard data retrieval, it was necessary to determine whether a user could perform actions that change the system state, such as adding or deleting. To further secure the system, we introduced our own authorization attribute, EntityAuthorizeAttribute, which is used to decorate methods in Application Services. This attribute checks whether the user has the appropriate permissions to perform an operation on an entity, allowing for more detailed access control. To handle the EntityAuthorizeAttribute, we utilised the interceptor mechanism available within the ABP.io framework. We created the EntityAuthorizationInterceptor, which uses the EntityAuthorizationService to check whether the user has the appropriate permissions to perform a given action:

public class EntityAuthorizationInterceptor : AbpInterceptor, ITransientDependency
{
    private readonly IMethodInvocationEntityAuthorizationService _methodInvocationAuthorizationService;

    public EntityAuthorizationInterceptor(IMethodInvocationEntityAuthorizationService methodInvocationAuthorizationService)
    {
        _methodInvocationAuthorizationService = methodInvocationAuthorizationService;
    }

    public override async Task InterceptAsync(IAbpMethodInvocation invocation)
    {
        Try
        {
            await AuthorizeAsync(invocation);
            await invocation.ProceedAsync();
        }
        catch (AbpAuthorizationException ex)
        {
            throw new UserFriendlyException(
                message: AbpAuthorizationUserFriendlyErrorMessages.AccessDenied,
                details: AbpAuthorizationUserFriendlyErrorMessages.AccessDeniedDetails,
                innerException: ex
            );
        }
    }
}

Summary

“Entity Access Policy” is our response to clients looking for an effective way to manage data access. With this system, we can ensure that every entity in the system is protected by appropriate access policies, minimising the risk of unauthorised access and enhancing the overall security of the application. This is an example of how advanced programming techniques can contribute to creating more reliable and secure IT solutions. Additionally, integrating our authorization system with the ABP.io platform proved to be a relatively simple and effective process. The ABP.io framework offers a rich set of tools and design patterns that facilitate the implementation of our advanced authorization and security requirements. Thanks to the modular architecture of ABP.io, we were able to easily introduce our own authorization components and integrate them with the existing mechanisms of the framework, allowing for seamless and rapid implementation. This, in turn, enabled our team to focus on delivering business value, without wasting time on the need to resolve integration issues. As a result, our “Entity Access Policy” has become an even more effective and reliable solution, working smoothly with the wider technology ecosystem of ABP.io.

Contact us.

If you need a partner in software development, we're here to help you.

We will respond to your enquiry immediately.