Giter Site home page Giter Site logo

finbuckle / finbuckle.multitenant Goto Github PK

View Code? Open in Web Editor NEW
1.2K 1.2K 250.0 2.66 MB

Finbuckle.MultiTenant is an open-source multitenancy middleware library for .NET. It enables tenant resolution, per-tenant app behavior, and per-tenant data isolation.

Home Page: https://www.finbuckle.com/multitenant

License: Apache License 2.0

C# 99.60% Shell 0.40%
aspnetcore aspnetcore-identity dotnet dotnetcore efcore entity-framework-core finbuckle middleware multitenant

finbuckle.multitenant's Issues

Null reference error on Add-Migration using MultiTenantIdentityDbContext

System.NullReferenceException: Object reference not set to an instance of an object.
   at Finbuckle.MultiTenant.EntityFrameworkCore.Shared.SetupModel(ModelBuilder modelBuilder, TenantContext tenantContext) in C:\...\Finbuckle.MultiTenant\src\Finbuckle.MultiTenant.EntityFrameworkCore\Shared.cs:line 93
   at Finbuckle.MultiTenant.EntityFrameworkCore.MultiTenantIdentityDbContext`1.OnModelCreating(ModelBuilder builder) in C:\...\Finbuckle.MultiTenant\src\Finbuckle.MultiTenant.EntityFrameworkCore\MultiTenantIdentityDbContext.cs:line 74

Just looks like there needs to be some null-conditional checks added to allow migrations to apply happily.

var rightExp = Expression.Constant(tenantContext.Id, typeof(string));

should be

var rightExp = Expression.Constant(tenantContext?.Id, typeof(string));

I have a branch going I can submit a PR for (just need contributor permissions).

Allow custom multitenant stores and strategies to specify service lifetime

In 1.2 all multitenant stores and strategies are registered as singletons. This works fine for some cases, but other cases make more sense to have a scoped (or maybe even transient) lifetime, e.g. dbcontexts registered with AddDbContext are scoped.

WithStore<T>
WithStore<T>(ServiceLifetime lifetime, params object[] parameters)

The other WithStore and WithStrategy variants would add a similar lifetime parameter. The built in store and strategies would be configured (and documented) with the most sensible lifetime.

API Sample

Will there be a sample of implementing a MultiTenant API?

Specifically an example of a MultiTenant scenario where there's a tenant database for tenant lookup and a database per tenant?

Improve Host Strategy

Current host strategy only finds the first segment of the host. Would be better to allow more flexible options based on a template/regex to allow main domain, nth segment, or nth segment from the end.

Implementing template pattern such as *.tenant.xyz.? where:

  • tenant is a placeholder for the identifier
    • is a wildcard meaning 0 or more segments (including the period) and can only be on one side of the tenant placeholder
  • ? always means exactly 1 segment
  • xyz indicates any other non period characters, carried directly into the regex.

Support case sensitive InMemoryTenantStore when using WithInMemoryStore

Currently WithInMemoryStore adds InMemoryMultiTenantStore to the services collection using the default case insensitive option.

Would like to add an optional bool parameter ignoreCase to WithInMemoryStore, defaulting to true, which is passed to the InMemoryMultiTenantStore constructor.

Remote authentication problem

Request handlers aren't being detected due to a change from using IsAssignableFrom to is as part of a refactor. Corrected in pr #45

Support custom route parameter name when using WithRouteStrategy

RouteMultiTenantStrategy supports a custom route parameter name via its contructor, but WithRouteStrategy does not use it.

Would like to add an optional string parameter routeParameter to WithRouteStrategy which defaults to null and is passed to the RouteMultiTenantStrategy constructor.

Add MultiTenant support for generic IdentityDbContext<TUser, TRole, TKey, TUserLogin, TUserRole, TUserClaim, TRoleClaim, TUserToken>

Per: https://github.com/entrypointsoft/AspNet.Identity.EntityFramework6/blob/master/src/AspNet.Identity.EntityFramework6/IdentityDbContext.cs

An implementation supporting the more explicit generic class would be useful.

I've got a branch going for this locally that I'm playing with at the moment.

Edit: FYI, doesn't look like I have the ability to push branches up with the access token provided. I'll leave anything pertaining to an issue in the branch form: feature/{issue#}-{description} and submit PRs in the dev.

Forked. PR submitted (after failing to sign initial commit)

Add logging support.

Need to add logging throughout the tenant resolution and Entity Framework flows. This will use standard ASP.NET Core logging.

Switch to Apache 2.0 license

In order to better align with official .NET Core code this project should switch from an MIT license to an Apache 2.0 license.

Multiple external logins cause PK error in AspNetUserLogins

Hi Andrew - great project!

I am just using to explore ideas at present, stumbled upon what I think is a minor issue (read: easy fix) with external logins .. consider 2 tenants, with one external user, a customer of both tenants

The AspNetUserLogins table has a primary key defined on LoginProvider and ProviderKey only - so this external user can only exist once, associated to the first tenant only

The TenantId column is added to AspNetUserLogins as expected, and the query to find an existing external login does filter as expected WHERE TenantId = ? AND LoginProvider = ? AND ProviderKey = ? ...so not finding the user for the 2nd tenant, then attempting the failing insert (a duplicate primary key thing)

So the fix for me was just to drop/create this PK but now as a combination of LoginProvider, ProviderKey, and TenantId .. I just updated my database directly for now but will eventually add to a migration on the context

Thanks

Question: default fallback tenant identifier

Which is the best way to implement a default fallback identifier with host subdomain strategy?

Suppose I registered *.domain.com in your dns. I have two tenants; tenant 1 and tenant2. I would like to redirect the user to the www.domain.com url if the tenant resolver does not find any tenant registered.

Sorry for the english. Thanks!

Hostname strategy

Hi

I am struggling to get the hostname strategy working. I'm using the EFCore InMemory store, which I populate with "TenantInfo" items. I've assumed the hostname should be set against the "Identifier" property so my setup looks like the following:

        public void ConfigureServices(IServiceCollection services)
        {
            // Register options
            services.AddOptions();

            services.AddMediatR(typeof(RetrieveTenants.Handler).Assembly);

            #region Tenant Db Configuration

            // Tenant database connection
            services.AddDbContext<ConfiguratorDbContext>(options =>
                options.UseSqlServer(Configuration["TenantDbConnection:ConnectionString"]));

            #endregion

            services.AddLogging();
            services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
                .AddFluentValidation(cfg =>
                {
                    cfg.RegisterValidatorsFromAssemblyContaining(typeof(AbstractValidator<>));
                });

            #region Multitenancy configuration

            services.AddMultiTenant()
                .WithEFCoreStore<StoreDbContext, AppTenantInfo>()
                .WithHostStrategy();

            #endregion

            #region Application Db Configuration

            // Register the db context, but do not specify a provider/connection string since
            // these vary by tenant.
            services.AddDbContext<WintrixDbContext>();

            #endregion
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseStaticFiles();
            app.UseMultiTenant();
            app.UseMvc();

            SetupStore(app.ApplicationServices);
        }

        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"]
                })
                .GetAwaiter()
                .GetResult()
                .Tenants;

            foreach (var tenant in tenants)
            {
                store.TryAddAsync(new TenantInfo(tenant.Id, tenant.Hostname, tenant.Name, tenant.ConnectionString, null)).Wait();
            }
        }

Currently I seem to get no tenantInfo when I call:

HttpContext.GetMultiTenantContext()?.TenantInfo;

My setup is meant to use our tenant database to get the tenant configuration data including connection string and hostnames. I then load this in to a TenantInfo objects with the hostname as the Identifier and insert this in to an EFCore store.

That part seems to work ok becuase I see all the tenants if I query the StoreDbContext directly.

I've added bindings in IIS for each host name and added entries to my host file.

Each tenant has a separate database which should then be resolved by the tenant connection string in the EFCoreStore, but as I said above, in my controller the tenantInfo is currently null?

Any help would be much appreciated

I guess one thing I thought about, was in your hostname sample you only replaced the subdomain, in my case the whole host name is different.

i.e.

www.something.com
another.domain.net

Andy

Creating new tenant and applying changes without restart

Hi,

Everytime I create a new tenant I have to restart the web application so the startup.cs would run again and the new tenant configuration could be applied.

If I create the tenant without restarting I can't access the new tenant website directly as I have to restart to be able to access it.

here is the code I'm using to create the new tenant.
// give him the code you're using

I tried to go through documentation but couldn't find the solution.

thanks

Known Issues with Finbuckle.MultiTenant and ASP.NET Core Identity

Hello, this announcement is to describe known issues with Finbuckle.MultiTenant and ASP.NET Core Identity. The first two are specific to theIdentityDataIsolationSample project and the third is a bug in Identity itself. These items will be resolved future releases, but since some of them are dependent on ASP.NET Core I can't say for sure when that will be.

  1. The sample configures Google authentication as a social login provider, but fails to call WithRemoteAuthentication after AddMultiTenant. This results in a user not being set up with the correct tenant when forwarded back to the app from Google. To fix this add a call to WithRemoteAuthentication after AddMultiTenant in the ConfigureServices method.

  2. The Identity pages do not render correctly. This is due to the sample application using Bootstrap 4 for layout whereas Identity assumes Bootstrap 3 by default. ASP.NET Core 2.2 is expected to add support for both Bootstrap 3 & 4 to Identity.

  3. Failed authentication doesn't redirect to the correct sign in page. This is due to abug in Identity and can be worked around as described here.

Strange bug regarding WithRemoteAuthentication

I am using a social login provider (google in this case) All works well with the following configuration

            services.AddMultiTenant()
                .WithStrategy<TenantStrategy>()
                .AddPerTenantCookieAuthentication()
                //.WithRemoteAuthentication()
                .AddPerTenantSocialLogins()
                ;

            services.Replace(ServiceDescriptor.Singleton<IAuthenticationSchemeProvider, MultiTenantAuthenticationSchemeProvider>());
            //services.Replace(ServiceDescriptor.Scoped<IAuthenticationService, MultiTenantAuthenticationService>());
            services.TryAddSingleton<IRemoteAuthenticationStrategy, RemoteAuthenticationStrategy>();

WithRemoteAuthentication has 3 methods it calls. For some reason the replacement for IAuthenticationService makes the 'signin-google' route redirect to the refererrer instead of the callbackurl.

BTW, it works for me as well without the .WithRemote, I guess is it because I am using a custom host strategy.

WRONG:
afbeelding

CORRECT (with middle call not made)
afbeelding

The challenge seems ok, but then again, the challenge 302 is not human readable. My fix for me is to remove the WithRemoteAuthentication altogether, as it works without for me, and I don't understand what it's doing.

I hope there is enough info here, netcore 2.1

Refactor route strategy support

Currently using the route strategy requires special logic in the middleware and a special version of UseMultiTenant which takes an Action<IRouteBuilder>.

A better approach would be to move this logic into the RouteMultiTenantStrategy class and to pass the Action<IRouteBuilder> into WithRouteStrategy method. Would require updates to the strategy, builder, and unit tests.

"Global" login page possible?

Hi

I was wondering whether it would be possible to have a generic / global login page for all users, and redirect to the correct tenant for that user after login. This would preclude users having to know the tenant name.

In other words, all users login at example.com/Account/Login.
On successful login, redirect to example.com/{tenant-associated-with-user}/Home
?

This would of course require a shared identity DB, and user names would have to be unique across all tenants, but it would be consistent with most multi-tenant apps that I've seen, and be less confusing to new users.

The alternative of having a landing page listing all tenants is suboptimal (to me), as it advertises who all your customers are.

I'm happy to tinker away if you think it would be possible, but perhaps you can see some structural reasons why it wouldn't work.

Thanks
Wojt.

Clean up namespaces for a 2.0.0 release.

For an eventual 2.0 release I want to clean up the namespaces for code that is expected to be usable by the library user. Right now its a mix of Finbuckle.MultiTenant, Finbuckle.MultiTenant.AspNetcore, etc...
I'm leaning toward it all being Finbuckle.MultiTenant for simplicity.

1.2.0 release and upcoming new documentation/website

Hello,

1.2.0 as just released with improved support for ASP.NET Core Identity, a sample project for Identity, and several bug fixes.

Also just as an FYI I'm working on a new website and project documentation--hope to get that out soon.

Repository adjustment for v1.1.0 release.

Hello, apologies to anyone with a fork, but in order to clean up the repo I decided to bump the master branch back a commit before applying the final release merge. I will try to avoid this as much as possible going forward.

Thanks.

Add support for ASP.NET Core remote authentication

While we can customize remote (OAuth based and OpenId Connect) authentication related options, this is not compatible with some multitenant strategies due to callback URLs which might prevent tenant resolution.

Want to build in support for this somehow.

Same tenant always for shared datasource regardless of current tenant

Discovered a bug related to the default caching behavior of EFCore. The global query filter is configured only for the first tenant to initialize the context, after which all contexts filter for the tenant id or that first tenant. This can be fixed by implementing a custom IModelCacheKeyFactory which accounts for the tenant.

Add support for IOptions<T> and IOptionsSnapshot<T> and Per Tenant Options

The current implementation of WithPerTenantOptionsConfig relies on a custom implementation of IOptionsCache, however OptionsManager (which IOptions and IOptionsSnapshot resolve to) uses an internal non DI version of IOptionsCache.

Approach 1 : Get OptionsManager to use DI for IOptionsCache. I have an issue opened with aspnetcore about this here.

Approach 2: Implement IOptions and IOptionsSnapshot, i.e. OptionsManager, myself so that it uses DI for IOptionsCache. Would have to then be registered instead of the default implementation--maybe just for T which have registered using WithPerTenantOptionsConfig.

Using ASP.Net Core, the sample didn't work, with SigninManager

I am using the IdentityCore like following:

services.AddIdentityCore<ApplicationUser>()
                .AddUserStore()
                .AddSignInManager()
                .AddDefaultTokenProviders();

When I use the following code with it:

           services
                .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);

            services.AddSingleton<IMultiTenantStore>((serviceProvider) =>
            {
                var cfg = serviceProvider.GetService<List<TenantConfiguration>>();
                return new TenantResolver(cfg.ToArray());
            });
            services.AddMultiTenant()
                .WithHostStrategy()
                .AddPerTenantCookieAuthentication();



public static MultiTenantBuilder AddPerTenantCookieAuthentication(this MultiTenantBuilder builder)
        {
            return builder.WithPerTenantOptions<CookieAuthenticationOptions>((options, tenantContext) =>
                {
                    // Set a unique cookie name for this tenant.         
                    options.Cookie.Name = tenantContext.Id + "-cookie";
                });
        }

I get the following error: InvalidOperationException: No sign-in authentication handler is registered for the scheme 'Identity.Application'. The registered sign-in schemes are: cookies. Did you forget to call AddAuthentication().AddCookies("Identity.Application",...)?


Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
Microsoft.AspNetCore.Identity.SignInManager<TUser>.SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod)
Profile.Controllers.AccountController.Registreer(RegisterViewModel model, string returnUrl) in AccountController.cs

                        await signInManager.SignInAsync(user, isPersistent: false);

If I change the scheme to this:

services
                .AddAuthentication("Identity.Application")
                .AddCookie("Identity.Application");

I can log in. Am I configuring something wrong? I would rather not hard code this value, to this one value I can't change.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.