Open Closed

Best Way to Enable/Disable an EF Global Filter Based on User Permissions #7830


User avatar
0
maksym created
  • ABP Framework version: v7.3.3
  • UI Type: Blazor Server
  • Database System: EF Core SQL Server
  • Tiered (for MVC) or Auth Server Separated (for Angular): yes
  • Exception message and full stack trace:
  • Steps to reproduce the issue:

Inspired by this documentation.

What is the best way to enable/disable a global EF filter for particular users based on their permissions?

In this particular case, we have created a ITestData interface which domain objects can inherit from and implement bool IsTestDate { get; set; } that states whether a particular line in the database is test data or not. Used in some tests we have in production. Like so:

class SomeUserData : FullyAuditedEntity, ITestData
{
	public string UserData1 { get; set; }
	public string UserData2 { get; set; }

	public bool IsTestData { get; set; } = true; // <- come from ITestData
}

We don't want regular users to see this test data, so we want to tie it with a permission.

We do that by creating a middleware that enables/disables the ITestData EF global filter if the user has or doesn't have the permission assigned respectively.

public class TestDataPermissionMiddleware : IMiddleware, IScopedDependency
{
    private readonly ILogger<TestDataPermissionMiddleware> _logger;
    private readonly IAuthorizationService _authorizationService;
    private readonly IDataFilter<ITestData> _dataFilter;

    public TestDataPermissionMiddleware(ILogger<TestDataPermissionMiddleware> logger, IAuthorizationService authorizationService, IDataFilter<ITestData> dataFilter)
    {
        _logger = logger;
        _authorizationService = authorizationService;
        _dataFilter = dataFilter;
    }
    
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        try
        {
            bool granted = await _authorizationService.IsGrantedAsync(SharedEntitiesPermissions.TestDataAccess.Default);
            if (!granted)
                _dataFilter.Enable();
            else
                _dataFilter.Disable();
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "TestDataPermissionMiddleware: Error occurred.");
        }

        await next(context);
    }
}

Here is the data filter code:

protected bool IsTestDataFilterEnabled => DataFilter?.IsEnabled<ITestData>() ?? true;

protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
{
	if (typeof(ITestData).IsAssignableFrom(typeof(TEntity)))
	{
		return true;
	}

	return base.ShouldFilterEntity<TEntity>(entityType);
}

protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
{
	var expression = base.CreateFilterExpression<TEntity>();

	if (typeof(ITestData).IsAssignableFrom(typeof(TEntity)))
	{
		Expression<Func<TEntity, bool>> isTestDataFilter = e => !IsTestDataFilterEnabled || !EF.Property<bool>(e, nameof(ITestData.IsTestData));
		expression = expression == null
			? isTestDataFilter
			: CombineExpressions(expression, isTestDataFilter);
	}

	return expression;
}

It works fine, but I was wondering if there is a better way of doing this that you could recommend?


1 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can add permission claims to the current user and then get the claim in the EF Core DB context. This will be easier.

    But this requires a dynamic claims system if you deny permission from a user.

    https://abp.io/docs/latest/framework/fundamentals/authorization?_redirected=B8ABF606AA1BDF5C629883DF1061649A#claims-principal-factory https://abp.io/docs/latest/framework/fundamentals/dynamic-claims?_redirected=B8ABF606AA1BDF5C629883DF1061649A

Made with ❤️ on ABP v9.1.0-preview. Updated on December 26, 2024, 06:07