Our solution is monolith-tiered. All of our services connect to the same database. We just have different environments like DEV/QA/PROD and each have their own databases. The DEV database is suddenly showing this error.
This includes not only the Host.Api project in our deployed azure environment that connects to the DEV database, but also when I remotely connect my local running Host.Api project on my computer to the DEV database.
The errors are coming from the Host.Api project.
Yeah we seem to have that (screenshot below). We never had a problem with it until something random happened. And now this issue only comes up when we connect to that very specific database. When connecting to other databases it works fine. Db Migrations don't help and clearing all the features tables doesn't help either.
We saw some other support tickets that also had the same issue, where they couldn't identify where the problem came from, but they were able to solve the issue without communicating the solution.
It seems that for whatever reason, we are having the issue from the above screenshot in only one of our databases (DEV database) and all other databases, including local developer databases are working perfectly fine.
If I connect my Host project to that database, I get the same issue. I couldn't find anything strange in the tables. I've tried truncating all the Features relates tables (Features, FeatureGroups and FeatureValues) to no avail.
It seems that this issue only comes from accessing the application-configuration endpoint. And since blazor always goes through that endpoint first, we can't even properly run our blazor app with this issue.
Have you had this issue in the past? Were you able to fix it for others too?
We also tried to import the Features module to add breakpoints to some suspected code, but we could only import the FeatureManagement module. Is there a way to import the Features module via ABP-CLI or ABP-Suite? We can't find that module using abp list-modules
command.
It seems that the actual error came from the singular permission grant check and from returning undefined when for the blazor app.
Removing this code seems to have fixed the issue (this is code that used to be useful, but not anymore, in our codebase).
Closing for now, as long as the issue has been resolved.
I created a new Tiered Blazor project and added a custom permission value provider that simply returns all permissions as granted and it worked fine.
But in our current solution, even the "grant all" approach has the same error.
API HOST side
Blazor Side
We created a custom PermissionValueProvider for our specific requirements, and even though the CheckAsync functions appear to return correct result values, it still fails with Forbidden error.
The curious thing is that permissions are required for the user for the menu items, so if I add scopes (our permission system) to the user, the ScopedRolesPermissionValueProvider properly add those permissions to the user and the user is now able to see the menu items.
But if we run api calls that use the Authorize
attribute, it fails with the Forbidden error.
code for custom PermissionValueProvider:
public class ScopedRolesPermissionValueProvider : IPermissionValueProvider, ITransientDependency
{
public string Name => "ScopedRolePermission";
private const string ADMIN_CLIENT_ID = "Fixhub_Web";
private readonly IServiceProvider _serviceProvider;
public ScopedRolesPermissionValueProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context)
{
var clientId = context.Principal?.FindFirst(AbpClaimTypes.ClientId)?.Value;
if (clientId is null || clientId == ADMIN_CLIENT_ID)
{
return PermissionGrantResult.Undefined; // We should let other PermissionValueProvider decide...
}
var userId = context.Principal?.FindFirst(AbpClaimTypes.UserId)?.Value;
if (userId == null)
{
return PermissionGrantResult.Undefined;
}
IPermissionContextManager permissionContextManager =
_serviceProvider.GetRequiredService<IPermissionContextManager>();
PermissionGrantResult permissionGrantResult =
await permissionContextManager.IsScopeGrantedAsync(userId, context.Permission?.Name);
return permissionGrantResult;
}
public async Task<MultiplePermissionGrantResult> CheckAsync(
PermissionValuesCheckContext context
)
{
var permissionNames = context.Permissions.Select(x => x.Name).Distinct().ToArray();
Check.NotNullOrEmpty(permissionNames, nameof(permissionNames));
var userId = context.Principal?.FindFirst(AbpClaimTypes.UserId)?.Value;
if (userId == null)
{
return new MultiplePermissionGrantResult(permissionNames);
} // Returns all permissions as Undefined.
IPermissionContextManager permissionContextManager =
_serviceProvider.GetRequiredService<IPermissionContextManager>(); // as PermissionContextManager;
return await permissionContextManager.IsScopeGrantedAsync(userId, permissionNames);
}
}
extension method for IsScopeGrantedAsync:
public static async Task<MultiplePermissionGrantResult> IsScopeGrantedAsync(
this IPermissionContextManager manager,
string userId,
string[] permissionNames
)
{
var scopedRolesAllowedPermissions = await manager.GetUserCurrentScopedPermissions(userId);
var result = new MultiplePermissionGrantResult();
foreach (var item in permissionNames)
{
result.Result.Add(
item,
item != null && scopedRolesAllowedPermissions.Contains(item)
? PermissionGrantResult.Granted
: PermissionGrantResult.Undefined
);
}
return result;
}
Results returned seem good:
How we add the dependency in our ApplicationModule.cs file
Basically browsing any page that either has the [Authorize]
attribute or that calls an api endpoint that has the [Authorize]
attribute fails with forbidden even if the user seems to have all the correct permissions assigned by our custom permission value provider.
This only happens through the blazor UI. Going directly through the API is perfectly fine. We even have a completely separate ReactJS app that connects to those endpoints and it works fine.
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?
Hi,
I tried using the Inject attribute and it still didn't work.
I've reproduced the error on a clean ABP Solution, here is the link: https://github.com/maksym-gryb/ABPTestSolution
Please try it out and let me know how we can resolve this issue in our codebase.
Hi,
after fumbling around with this feature management module, I found out that it was because we were changing the values via the "Editions" tab, whereas we had to change them in Tenants and use the "Manage Host Features" button.
As we didn't enable multitenancy, nor do we use it, it was rather confusing to know what to look for.
This ticket can be marked as resolved.
I've added virtual and async to the methods but the results are still the same.
As mentioned previously, the issue isn't in the feature management system itself not working, it's the fact that the feature which is set correctly in the database to "true"
Is still being read as "false" in abp via the feature checker