Activities of "raif"

Create extension for CurrentUser in order to get OrganizationIds from token.

public static class CurrentUserExtensions
{
    public static string[] GetOrganizationUnits(this ICurrentUser currentUser)
    {
       Claim[] claims = currentUser.FindClaims("organization_unit");
       
       return claims.Select(c => c.Value).ToArray();
    }
}

Add query filters for EF Core

protected override bool ShouldFilterEntity< TEntity >(IMutableEntityType entityType)
{
    if (typeof(IHasAccessControl).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(IHasAccessControl).IsAssignableFrom(typeof(TEntity)))
    {
        Expression< Func < TEntity, bool > > hasAccessControlFilter = e => CurrentUser.GetOrganizationUnits().Contains(EF.Property< string >(e, "OrganizationId")) || CurrentUser.Id == (EF.Property< string >(e, "OwnerId"));
        
        expression = expression == null ? hasAccessControlFilter : CombineExpressions(expression, hasAccessControlFilter);
    }

    return expression;
}

Let suppose we have microservices A and B. A for identity, auditing, saas etc, basic IT needs Where B is business microservice.

After creating a new "project" object in microservice B, I want to assign it to a specific person or a specific organizational unit. (see global filter implementation)

So we needed lookup values for organization units (it can be logins, tokens, roles, claims for another use cases)

Information exchange between A and B can be

  • Synchronous
  • Asynchronous (in this specific case data can flow one way)

Again let assume that we would like keep asynchronous communication where we were using UserEtos

So, every time the user is updated in microservice A, I want to update the user information in microservice B via distributed events.

However UserEto's doesn't carry information about

  • Claims
  • Roles
  • Logins
  • Tokens
  • Organization Units

So how can we access asynchronously the above information about the user ?

UserLookupService from Volo.Abp.Users package only forces IUser interface which is doesn't force claims, roles, tokens, ou etc..

public abstract class UserLookupService<TUser, TUserRepository> : IUserLookupService<TUser>, ITransientDependency where TUser : class, IUser where TUserRepository : IUserRepository<TUser>

OU and roles are mainly used to organize permissions and your module should only need to the permission system dependency (it already has). These details are internals of the Identity module.

I think permission system dependency looks enough if you are building "policy based" authorization but not "row level" authorization.

Let's assume use case where we are adding query filters according users organization unit detail. We may want to the user access more or less data according to their hierarchy in the organizational unit. Policy authorized one end point should return all organizational units lookup values for assignment.

Let's define interface for this;

    public interface IHasAccessControl
    {
        public string OwnerId { get; }

        public string OrganizationId { get; }
    }

Implement interface to the Aggregate Root

public class Project : AuditedAggregateRoot<Guid>, IMultiTenant, IHasAccessControl
{
    public virtual string Name { get; protected set; }
    // ...
    public virtual Guid? TenantId { get; protected set; }
    public virtual string OwnerId { get; protected set; }
    public virtual string OrganizationId { get; protected set; }

    public virtual void SetOwnerId([NotNull] string ownerId)
    {

    }
    
    public virtual void SetOrganizationId([NotNull] string organizationId)
    {

    }
}

Let's add organization id information to the token.

public class OrganizationUnitPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency
{
    public async Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
    {
        var identity = context.ClaimsPrincipal.Identities.FirstOrDefault();

        var userId = identity?.FindUserId();        
        if (userId.HasValue)
        {
            var userService = context.ServiceProvider.GetRequiredService< IdentityUserManager >(); 

            var user = await userService.FindByIdAsync(userId.ToString());

            if (user != null)
            {
                user.OrganizationUnits
                    .Select(u => u.OrganizationUnitId).ToList()
                    .ForEach(unit => identity.AddClaim(new Claim(type: "organization_unit", value: unit.ToString())));
            }
        }
    }
}

Add short cut for identityServer (https://github.com/abpframework/abp/pull/7998)

Configure<AbpClaimsServiceOptions>(options =>
{
    options.RequestedClaims.AddRange(new[] { "organization_unit" });
});

Ok thx after login it is working again, but I didn't perform any "log out" operation ? Do we need to refresh our login time to time ? Is there any kind of necessity ?

login-info returns with null values

Thanks for the information. It's really easy to override. Thanks for abp modularization.

What I want to understand is whether this change will cause a problem in the operation of template engines (razor, scribian).

Do i need to consider to check AbpTextTemplatingScribanModule or AbpTextTemplatingRazorModule as well ?

Doh, at least i'm not the only guy who waiting that fix

I recommend you to use the second database for Hangfire server.

Yeah, you are right using separate database for Hangfire most effective solution.

BTW, I recommend that you install Volo.Abp.BackgroundWorkers.HangFire package in the startup project(.MVC, .HttpApi.Host etc..). this way it does not affect the DbMigrator project

Noted.

Cheers

Hi,

I think you should create a database first, hangfire will not create a database for you.

See: https://github.com/HangfireIO/Hangfire/blob/master/src/Hangfire.SqlServer/Install.sql

Hi, Of course we need to create DB first in place. Dbmigrator console application already exists to facilitate this database create operation, right ?

   public async Task StartAsync(CancellationToken cancellationToken)
    {
        using (var application = await AbpApplicationFactory.CreateAsync<hangfireDbMigratorModule>(options =>
        {
           options.Services.ReplaceConfiguration(_configuration);
           options.UseAutofac();
           options.Services.AddLogging(c => c.AddSerilog());
        }))
        {
            **await application.InitializeAsync();**

            await application
                .ServiceProvider
                .GetRequiredService<hangfireDbMigrationService>()
                .MigrateAsync();

            await application.ShutdownAsync();

            _hostApplicationLifetime.StopApplication();
        }
    }

So let me go step by step. Console application is going to initialize modules. Right ?

That means whenever you use Volo.Abp.BackgroundWorkers.HangFire and/or Volo.Abp.BackgroundJobs.HangFire you will also run OnPreApplicationInitialization method. Right ?

public async override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
{
    var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;
    if (!options.IsEnabled)
    {
        var hangfireOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpHangfireOptions>>().Value;
        hangfireOptions.BackgroundJobServerFactory = CreateOnlyEnqueueJobServer;
    }
    
    await context.ServiceProvider
        .GetRequiredService<IBackgroundWorkerManager>()
        .StartAsync(); 
}

public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
    AsyncHelper.RunSync(() => OnPreApplicationInitializationAsync(context));
}

More or less whenever you start BackgroundWorkerManager you will also going to try to create **BackgroundJobServer **

await context.ServiceProvider
    .GetRequiredService<IBackgroundWorkerManager>()
    .StartAsync();

And **BackgroundJobServer **ceration requires JobStorage .Btw BackgroundJobServer constructor used in the abp package is the obsolete one

private BackgroundJobServer CreateJobServer(IServiceProvider serviceProvider)
{
    Storage = Storage ?? serviceProvider.GetRequiredService<JobStorage>();
    ServerOptions = ServerOptions ?? serviceProvider.GetService<BackgroundJobServerOptions>() ?? new BackgroundJobServerOptions();
    AdditionalProcesses = AdditionalProcesses ?? serviceProvider.GetServices<IBackgroundProcess>();

    return new BackgroundJobServer(ServerOptions, Storage, AdditionalProcesses,
        ServerOptions.FilterProvider ?? serviceProvider.GetRequiredService<IJobFilterProvider>(),
        ServerOptions.Activator ?? serviceProvider.GetRequiredService<JobActivator>(),
        serviceProvider.GetService<IBackgroundJobFactory>(),
        serviceProvider.GetService<IBackgroundJobPerformer>(),
        serviceProvider.GetService<IBackgroundJobStateChanger>()
    );
}

If you don't define any JobStorage that will cause exception about JobStorage can't be null exception

GlobalConfiguration.Configuration
    .UseSqlServerStorage(configuration.GetConnectionString("Default"));

If you define JobStorage then it will going to execute https://github.com/HangfireIO/Hangfire/blob/master/src/Hangfire.SqlServer/Install.sql scripts.

And as you already aware of that, scripts requires database in first place.

And if you remember we are about create DB :)

Long of short: Your Volo.Abp.BackgroundWorkers.HangFire and/or Volo.Abp.BackgroundJobs.HangFire are killing DbMigrator functionality by their nature of design.

Steps to reproduce the issue :

  1. Create new application by using application template
  2. Add Hangfire.SqlServer package for job persistence
  3. Add Volo.Abp.HangFire (this should enough to force convert existing background jobs into the hangfire one like tokencleanup etc)
  4. Define connection string for job storage
  5. Run Db Migrator

Ok, thx for the fix and update, i will look forward 5.0 release

/bump

Showing 1 to 10 of 28 entries
Made with ❤️ on ABP v9.1.0-rc.1. Updated on January 17, 2025, 14:13