Finbuckle / Finbuckle.MultiTenant

Finbuckle.MultiTenant is an open-source multitenancy middleware library for .NET. It enables tenant resolution, per-tenant app behavior, and per-tenant data isolation.
https://www.finbuckle.com/multitenant
Apache License 2.0
1.3k stars 265 forks source link

Items not set in WithPerTenantOptions #100

Closed mcinnes01 closed 4 years ago

mcinnes01 commented 5 years ago

Hi,

I know there was a bug you fixed where Items weren't being set, so I've pulled master down so it is correctly setting the items on TenantInfo. However in WithPerTenantOptions, when I try to access items, there are no items available.

            services.AddMultiTenant()
                .WithEFCoreStore<StoreDbContext, AppTenantInfo>()
                .WithStrategy(ServiceLifetime.Singleton, sp => new DomainStrategy(sp.GetService<ILogger<DomainStrategy>>()))
                .WithPerTenantOptions<IdentityServerAuthenticationOptions>((options, tenantInfo) =>
                {
                    // Since we are using the route strategy configure each tenant
                    // to have a different cookie name and adjust the paths.
                    if (tenantInfo.Items.TryGetValue(HostnameType.IdpHostname.ToString(), out var idpHostname))
                        options.Authority = $"http://{idpHostname}";

                    options.RequireHttpsMetadata = false;
                    options.ApiName = $"{tenantInfo.Id}-api";
                });

This is where I set them called from the Configure method:

        private void SetupStore(IServiceProvider sp)
        {
            var scopeServices = sp.CreateScope().ServiceProvider;
            var store = scopeServices.GetRequiredService<IMultiTenantStore>();
            var mediator = scopeServices.GetRequiredService<IMediator>();

            var tenants = mediator.Send(new RetrieveTenants.Query
                {
                    Environment = Configuration["TenantDbConnection:Environment"],
                    HostnameType = HostnameType.IdpHostname
                })
                .GetAwaiter()
                .GetResult()
                .Tenants;

            foreach (var tenant in tenants)
            {
                var items = new Dictionary<string, object>
                {
                    { HostnameType.Hostname.ToString(), tenant.Hostname },
                    { HostnameType.IdpHostname.ToString(), tenant.IdpHostname }
                };
                store.TryAddAsync(new TenantInfo(tenant.Id, tenant.Hostname, tenant.Name, tenant.ConnectionString, items)).Wait();
            }
        }
AndrewTriesToCode commented 5 years ago

@mcinnes01 Hm, your code here looks ok. Do you have a project I can look at to see the problem when the app is running?

As a test, I would set up a simple controller and in its dependency injection get IOptions<IdentityServerAuthenticationOptions> and check the values within the controller to see if they were set for the tenant.

It's possible that IdentityServer is using these options during setup when there is no tenant and caching them--thus no tenant specific options are ever created/used. In that case things will be a bit more complicated. I will have some time to look into this over the next few weeks.

AndrewTriesToCode commented 5 years ago

Hi, I was able to get this to work today using the IdentityServer4 Quick Start Sample 1 for the client credential flow. I will clean up my sample and post it on GitHub later. Here are some thoughts:

Good luck!

mcinnes01 commented 5 years ago

Hi

Great stuff, I'll try out your ideas above shortly and let you know how I get on. Does this deal with dynamically resolving the clients per tenant? I'd made an implementation of IClientStore whilst playing with it, and I resolved the tenant from the context to build tenant specific clients. I guess what would be cool is if I cached them and then resolved them by tenant id.

Good work and I look forward to seeing your example :)

Out of interest, in SaasKit there is a per tenant container implementation, is there something similar in Finbuckle or do you suggest a different approach, I know you mentioned IOptions, but I guess there are scenarios that won't lend well to that? Is a tenant scoped container something you've thought about incorporating?

Many thanks

Andy

AndrewTriesToCode commented 5 years ago

@mcinnes01, I haven't played with IClientStore yet--but I think MultiTenantDbContext (similar to MultiTenantIdentityDbCotext but without Identity stuff) could be used to good effect.

With respect a per-tenant container, do you mean a per-tenant dependency injection container? I haven't put too much thought into it. I thought maybe a "Tenant" lifetime option next to Singleton, Scoped, and Transient might be good, but using WithPerTenantOptions Scoped and Transient are already per-tenant. The only thing that a "Tenant" lifetime would potentially handle are Singleton items. I didn't have a need so I didn't pursue it.

I think SaasKit also does per tenant pipelines. Is that what you were referring to? I know that's how they did per tenant authentication in ASP.NET Core 1, but with ASP.NET Core 2 that method doesn't work well. I haven't looked into having a custom pipeline since I haven't needed it for anything.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.