Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multitenancy with TheIdServer #1084

Open
funkel1989 opened this issue Sep 18, 2023 · 14 comments
Open

Multitenancy with TheIdServer #1084

funkel1989 opened this issue Sep 18, 2023 · 14 comments
Labels
question Further information is requested

Comments

@funkel1989
Copy link

I am incredibly interested in what you have built with the Id Server.

Today I'm working on standing up and creating an Identity Server with there QuickStart UI that I'm slowly adding onto. I looked into your solution but there is no good way today to quickly add multitenancy support to TheIdServer that I can see, at least not by using your templates. I attempted to pull your entire code base down to see if I could get it to run but I failed for many hours.

Today I'm using Finbuckle for multitenancy on Identity servers which is very easy to setup. I have it configured for each tenant to have its own database and I have a seed script for each of those databases. By AspNetCore Entity framework context along with the persistentgrant context and Configuration context are all configured for multitenant support with IdentityServer.

My default tenant falls back to a certain Database if one is not entered but my pathing assumes that https://myUrl.com/TenantId/whateverExistingPathHere. and then i use a simple middleware to get my tenant for that request and then process to the page

app.UseMultiTenant();
app.Use(async (context, next) =>
{
    var multiTenantContext = context.GetMultiTenantContext<ExtendedTenantInfo>();

    var tenant = multiTenantContext?.TenantInfo;

    if (tenant != null && multiTenantContext?.StrategyInfo?.StrategyType == typeof(BasePathStrategy))
    {
        context.Request.Path.StartsWithSegments("/" + tenant.Identifier, out var matched, out var newPath);
        context.Request.PathBase = Path.Join(context.Request.PathBase, matched);
        context.Request.Path = newPath;
    }

    await next.Invoke();
});

I'd like to do the same with TheIdServer and see how this works for me. The idea I had is that eventually I'd have a tenant drop down on the blazer app to select my tenant and work from that tenant but it would always default to my primary like i have it now.

Would you be able to guide me on the different things I might have to override in TheIdServer to potentially accomplish this?

If successful I'd be willing to open a PR back into your repo with it setup to provide optional multiTenant configurations for other users of TheIdServer.

@funkel1989 funkel1989 added the question Further information is requested label Sep 18, 2023
@aguacongas
Copy link
Member

Hi, thanks to contribute.
I dont' know Finbuckle, so I can't really help.
Having a drop down to select the tenant in the blazor app means a lot of things regarding authentication and configuration per tenant. I'm not sure it's the best way to accomplish that. May be a simple page at the root level to redirect to the desired tenant is better and easier.
Else you can write a HttpMessageHandler to override the api path for the selected tenant and add in each HttpClient pipeline:

.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>()
.AddHttpMessageHandler<TenantMessageHandler>()

If you need a single DB for all tenants that also means rewriting all stores to get the tenant id and choose the good configuration.
If you'll have a DB per tenant, you'll need a way to choose the good DB connection string.

It's not a trivial operation. good luck :)

@aguacongas
Copy link
Member

There also the OIDC discovery document generator to rewrite per tenant if you override the request path. At least provide a new implementation for IServerUrls to replace the DefaultServerUrls

@funkel1989
Copy link
Author

@aguacongas I'm working on this multi tenency idea i've had with TheIdServer and need a bit of help.

I'm trying to create a factory for the AspnetCore User Manager and Role Manager that TheIdServer implements but I can't seem to find the code in your Repo. Could you please point me in the right direction?

Here is an example of what i'm trying to do with the base UserManager and RoleManager. The ultimate goal of this is to pass a specific instance of the ApplicationDbContext to this factory so the managers interact with the appropriate database.

public static class IdentityManagerFactory
{
    public static UserManager<User> CreateUserManager(IdentityContext context, IServiceProvider serviceProvider)
    {
        var userStore = new UserStore<User, Role, IdentityContext, string>(context);

        var optionsAccessor = new OptionsWrapper<IdentityOptions>(new IdentityOptions());
        var passwordHasher = new PasswordHasher<User>();
        var userValidators = new List<IUserValidator<User>>();
        var passwordValidators = new List<IPasswordValidator<User>>();
        var lookupNormalizer = new UpperInvariantLookupNormalizer();
        var identityErrorDescriber = new IdentityErrorDescriber();
        var logger = new Logger<UserManager<User>>(new LoggerFactory());

        return new UserManager<User>(
            userStore, optionsAccessor, passwordHasher, userValidators, passwordValidators,
            lookupNormalizer, identityErrorDescriber, serviceProvider, logger);
    }

    public static RoleManager<Role> CreateRoleManager(IdentityContext context)
    {
        var roleStore = new RoleStore<Role, IdentityContext, string>(context);

        var roleValidators = new List<IRoleValidator<Role>>();
        var lookupNormalizer = new UpperInvariantLookupNormalizer();
        var identityErrorDescriber = new IdentityErrorDescriber();
        var logger = new Logger<RoleManager<Role>>(new LoggerFactory());

        return new RoleManager<Role>(
            roleStore, roleValidators, lookupNormalizer, identityErrorDescriber, logger);
    }
}

@aguacongas
Copy link
Member

You cannot create a static factory. Instances must be created by DI. I guess you need a way to get the good connection string per tenant and don't forget TheIdServer also support MongoDB and RavenDB. I'm not sure it's the good way. May be the best choice is to add the tenant id on each entities and provides a service getting the tenantId to each stores so they can filter entities by the current tenant id. This way there no needs to create a new db per tenant. What you tink ?

@aguacongas
Copy link
Member

That said, you can override the default DI to provide the DB context, RavenDB IAsyncDocumentSession or Mongo DB IMongoDatabase per tenant using factories if you prefer.

for exemple:

public static class ServiceCollectionExtensions
{
     public static ServiceCollection AddTenants(this ServiceCollection services)
     {
            services.AddScoped(p =>
            {
                // return your DB context factory implementation by tenant
                var tenantService = p.GetRequiredService<IGetContextCurrentTenant>();
                return tenant.GetDbContext<ApplicationDbContext>();
            });
            // same for each DB context
           ...

            services.AddTransient(p =>
            {
                // returns your RavenDB IAsyncDocumentSession factory implementation by tenant
                var tenantService = p.GetRequiredService<IGetRavenDbAsynSessionForCurrentTenant>();
                return tenant.GetRavenDbAsyncSession();
            });

            services.AddTransient(p =>
            {
                // return your MongoDB IMongoDatabase factory implementation by tenant
                var tenantService = p.GetRequiredService<IGetMongoDbDatabaseForCurrentTenant>();
                return tenant.GetMongoDbDatabase();
            });

           return services
     }
}

@funkel1989
Copy link
Author

Hey @aguacongas,

Wanted to provide some context. After looking over your code I agree that TheIdServer would not benefit from built in multi-tenancy. I'm still proceeding to fit to to my needs though. Having said that I think I just have one more question for you for clarification. Is your OperationalDbContext the same as the PersistedGrantDbContext defined by duende?

@aguacongas
Copy link
Member

More or less except there's one table per grant kind but the goal is to store temporary tokens.

@aguacongas
Copy link
Member

By the way can you open a PR when your job is done ? I'm interested by your work even if that's not cover all DB kinds supported by TheIdServer nor all functionnalities.

@funkel1989
Copy link
Author

funkel1989 commented Oct 15, 2023

I'll share a Repo link with you to look at. I ended up starting from the Dotnet template you have as overall it was less confusing. I did run into an issue though. I'm attempting to create my own migrations and i'm getting an error when trying to create a migration for the PersistedGrantDbContext/OperationalDbContext:

dotnet ef migrations add InitialCreatePersistedGrant --context PersistedGrantContext --output-dir Migrations/PersistedGrant
Build started...
Build succeeded.
The Entity Framework tools version '7.0.9' is older than that of the runtime '7.0.12'. Update the tools for the latest features and bug fixes. See https://aka.ms/AAc1fbw for more information.
System.InvalidOperationException: No suitable constructor was found for entity type 'Claim'. The following constructors had parameters that could not be bound to properties of the entity type:
    Cannot bind 'reader' in 'Claim(BinaryReader reader)'
    Cannot bind 'reader', 'subject' in 'Claim(BinaryReader reader, ClaimsIdentity subject)'
    Cannot bind 'type', 'value' in 'Claim(string type, string value)'
    Cannot bind 'type', 'value', 'valueType' in 'Claim(string type, string value, string valueType)'
    Cannot bind 'type', 'value', 'valueType', 'issuer' in 'Claim(string type, string value, string valueType, string issuer)'
    Cannot bind 'type', 'value', 'valueType', 'issuer', 'originalIssuer' in 'Claim(string type, string value, string valueType, string issuer, string originalIssuer)'
    Cannot bind 'type', 'value', 'valueType', 'issuer', 'originalIssuer', 'subject' in 'Claim(string type, string value, string valueType, string issuer, string originalIssuer, ClaimsIdentity subject)'
    Cannot bind 'type', 'value', 'valueType', 'issuer', 'originalIssuer', 'subject', 'propertyKey', 'propertyValue' in 'Claim(string type, string value, string valueType, string issuer, string originalIssuer, ClaimsIdentity subject, string propertyKey, string propertyValue)'
    Cannot bind 'other' in 'Claim(Claim other)'
    Cannot bind 'other', 'subject' in 'Claim(Claim other, ClaimsIdentity subject)'
Note that only mapped properties can be bound to constructor parameters. Navigations to related entities, including references to owned types, cannot be bound.
   at Microsoft.EntityFrameworkCore.Metadata.Internal.ConstructorBindingFactory.GetBindings(IReadOnlyEntityType entityType, Func`5 bind, InstantiationBinding& constructorBinding, InstantiationBinding& serviceOnlyBinding)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.ConstructorBindingFactory.GetBindings(IMutableEntityType entityType, InstantiationBinding& constructorBinding, InstantiationBinding& serviceOnlyBinding)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.ConstructorBindingConvention.ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext`1 context)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalizing(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalizing(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelRuntimeInitializer.Initialize(IModel model, Boolean designTime, IDiagnosticsLogger`1 validationLogger)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func`1 factory)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
No suitable constructor was found for entity type 'Claim'. The following constructors had parameters that could not be bound to properties of the entity type:
    Cannot bind 'reader' in 'Claim(BinaryReader reader)'
    Cannot bind 'reader', 'subject' in 'Claim(BinaryReader reader, ClaimsIdentity subject)'
    Cannot bind 'type', 'value' in 'Claim(string type, string value)'
    Cannot bind 'type', 'value', 'valueType' in 'Claim(string type, string value, string valueType)'
    Cannot bind 'type', 'value', 'valueType', 'issuer' in 'Claim(string type, string value, string valueType, string issuer)'
    Cannot bind 'type', 'value', 'valueType', 'issuer', 'originalIssuer' in 'Claim(string type, string value, string valueType, string issuer, string originalIssuer)'
    Cannot bind 'type', 'value', 'valueType', 'issuer', 'originalIssuer', 'subject' in 'Claim(string type, string value, string valueType, string issuer, string originalIssuer, ClaimsIdentity subject)'
    Cannot bind 'type', 'value', 'valueType', 'issuer', 'originalIssuer', 'subject', 'propertyKey', 'propertyValue' in 'Claim(string type, string value, string valueType, string issuer, string originalIssuer, ClaimsIdentity subject, string propertyKey, string propertyValue)'
    Cannot bind 'other' in 'Claim(Claim other)'
    Cannot bind 'other', 'subject' in 'Claim(Claim other, ClaimsIdentity subject)'
Note that only mapped properties can be bound to constructor parameters. Navigations to related entities, including references to owned types, cannot be bound.

I tested it without my context override and it looks like it might exist in the unmodified code also, but I haven't determined if its something I've done. I'm also not sure what Claim model it is talking about, since i started with the Dotnet Template I don't have all the models easily assessable to me.

Here is my override context incase its relavant.

public class PersistedGrantContext : OperationalDbContext, IMultiTenantDbContext
{
    private const string DefaultSchema = "persisted_grant";

    private readonly IConfiguration _configuration;
    private readonly IWebHostEnvironment _environment;

    public PersistedGrantContext(DbContextOptions<OperationalDbContext> options, ExtendedTenantInfo tenantInfo, IConfiguration configuration, IWebHostEnvironment environment)
        : base(options)
    {
        TenantInfo = tenantInfo;
        _configuration = configuration;
        _environment = environment;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connectionString = TenantInfo.ConnectionString!;

        if (!_environment.IsProduction())
        {
            connectionString += "_Dev";
        }

        optionsBuilder.UseNpgsql(DatabaseExtensions.BuildPostgresConnectionString(_configuration, _environment, connectionString) ?? throw new InvalidOperationException());
        base.OnConfiguring(optionsBuilder);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.HasDefaultSchema(DefaultSchema);

        modelBuilder.ConfigureMultiTenant();

        base.OnModelCreating(modelBuilder);

        //foreach (var entity in modelBuilder.Model.GetEntityTypes())
        //{
        //    var tableName = entity.GetTableName().ToSnakeCase();
        //    modelBuilder.Entity(entity.Name).ToTable(tableName);

        //    foreach (var property in entity.GetProperties())
        //    {
        //        var columnName = property.Name.ToSnakeCase();
        //        modelBuilder.Entity(entity.Name).Property(property.Name).HasColumnName(columnName);
        //    }
        //}

        modelBuilder.Entity<AuthorizationCode>().IsMultiTenant();
        modelBuilder.Entity<ReferenceToken>().IsMultiTenant();
        modelBuilder.Entity<RefreshToken>().IsMultiTenant();
        modelBuilder.Entity<UserConsent>().IsMultiTenant();
        modelBuilder.Entity<DeviceCode>().IsMultiTenant();
        modelBuilder.Entity<OneTimeToken>().IsMultiTenant();
        modelBuilder.Entity<DataProtectionKey>().IsMultiTenant();
        modelBuilder.Entity<KeyRotationKey>().IsMultiTenant();
        modelBuilder.Entity<UserSession>().IsMultiTenant();
        modelBuilder.Entity<Saml2PArtifact>().IsMultiTenant();
        modelBuilder.Entity<BackChannelAuthenticationRequest>().IsMultiTenant();
    }

    public ITenantInfo TenantInfo { get; }
    public TenantMismatchMode TenantMismatchMode => TenantMismatchMode.Throw;
    public TenantNotSetMode TenantNotSetMode => TenantNotSetMode.Throw;
}

and the factory incase your curious

public class PersistedGrantContextFactory
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IConfiguration _configuration;
    private readonly IWebHostEnvironment _environment;

    public PersistedGrantContextFactory(IHttpContextAccessor httpContextAccessor, IConfiguration configuration, IWebHostEnvironment environment)
    {
        _httpContextAccessor = httpContextAccessor;
        _configuration = configuration;
        _environment = environment;
    }

    public PersistedGrantContext CreateDbContext(DbContextOptions<OperationalDbContext> options, ExtendedTenantInfo providedTenantInfo = null)
    {
        ExtendedTenantInfo tenantInfo = providedTenantInfo;

        if (tenantInfo == null && _httpContextAccessor.HttpContext != null)
        {
            tenantInfo = _httpContextAccessor.HttpContext.GetMultiTenantContext<ExtendedTenantInfo>()?.TenantInfo;
        }

        tenantInfo ??= new ExtendedTenantInfo();

        return new PersistedGrantContext(options, tenantInfo, _configuration, _environment);
    }
}

I also looked in your OperationalDbContext and I don't see any "Claim" type. Have you seen this before?

@aguacongas
Copy link
Member

No, not yet

@funkel1989
Copy link
Author

So i figured out the above problem. Some of your type names exactly match Duende Type names which caused imports to get screwed up. Claim was a type on a duende type name instead of string.

Next thing i'm having trouble understand is your ApplicationDbContext has the type of User and Role. But your UserManager code and Role Manager code accept IdentityRole and ApplicationUser. Where is the code that intercepts thie request of the UserManager/RoleManager to change this model to a User or a Role to match the type of the DbContext?

When i'm trying to see roles i get the following error:
Cannot create a DbSet for 'IdentityRole' because this type is not included in the model for the context.

which is because ApplicationDbContext has a DbSet for Role which doesn't inherit from IdentityRole.

I'm not finding any custom implementations of UserManager or RoleManager so i'm quite confused.

@aguacongas
Copy link
Member

There are custom UserStore and RoleStore instead in Aguacongas.TheIdServer.Identity project. They use IAdminStore implementations to access data so that don't depend on a DB type and I can even choose HttpStore

@funkel1989
Copy link
Author

Looking at these stores, is the type in the Database a Role and User or is it still IdentityRole and IdentityUser. I'm seeing conversions happening in places so i'm not sure what the end result is that is being committed. assuming IdentityRole and IdentityUser, but i might be missing something.

@aguacongas
Copy link
Member

Entities in DB are defined in Aguacongas.IdentityServer.Store/Entity.If you need to add/update some columns, you should update User. Role and other entities classes as needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants