Is there a simple way to customize IdentityServer4's configuration and operational dababase mapping? - identityserver4

I'm implementing an authentication server using IS4 with an Oracle database to store the configuration and operational data.
As far as I know, Oracle has a limitation of 30 characters for tables and columns names. Because of that, some of IS4's properties causes errors when applying the database migrations, such as:
AlwaysIncludeUserClaimsInIdToken
FrontChannelLogoutSessionRequired
BackChannelLogoutSessionRequired
UpdateAccessTokenClaimsOnRefresh
In order to override the default database mapping I've:
1 - made a copy of the original ConfigurationDbContext:
public class CustomConfigurationDbContext : CustomConfigurationDbContext<CustomConfigurationDbContext>
{
public CustomConfigurationDbContext(DbContextOptions<CustomConfigurationDbContext> options, ConfigurationStoreOptions storeOptions) : base(options, storeOptions){}
}
public class CustomConfigurationDbContext<TContext> : DbContext, IConfigurationDbContext
where TContext : DbContext, IConfigurationDbContext
{
private readonly ConfigurationStoreOptions storeOptions;
public CustomConfigurationDbContext(DbContextOptions<TContext> options, ConfigurationStoreOptions storeOptions)
: base(options)
{
this.storeOptions = storeOptions ?? throw new ArgumentNullException(nameof(storeOptions));
}
public DbSet<Client> Clients { get; set; }
public DbSet<IdentityResource> IdentityResources { get; set; }
public DbSet<ApiResource> ApiResources { get; set; }
public Task<int> SaveChangesAsync()
{
return base.SaveChangesAsync();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
base.OnConfiguring(optionsBuilder);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema(storeOptions.DefaultSchema);
modelBuilder.ApplyConfiguration(new ClientEntityTypeConfiguration());
}
private class ClientEntityTypeConfiguration : IEntityTypeConfiguration<Client>
{
public void Configure(EntityTypeBuilder<Client> builder)
{
builder.Property(p => p.AlwaysIncludeUserClaimsInIdToken).HasColumnName("ALWAYS_INC_USR_CLAIM_IN_TKN");
builder.Property(p => p.FrontChannelLogoutSessionRequired).HasColumnName("FRONT_LOGOUT_SESSION_REQ");
builder.Property(p => p.BackChannelLogoutSessionRequired).HasColumnName("BACK_LOGOUT_SESSION_REQ");
builder.Property(p => p.UpdateAccessTokenClaimsOnRefresh).HasColumnName("UP_ACCESS_TKN_CLAIM_ON_REFRESH");
}
}
}
2 - changed the Startup.cs:
var builder = services
.AddIdentityServer(options =>
{
...
})
.AddConfigurationStore<CustomConfigurationDbContext>(options =>
{
options.ConfigureDbContext = b =>
b.UseOracle(Configuration.GetConnectionString("Auth2Connection"), sql =>
sql.MigrationsAssembly(migrationsAssembly)
.MigrationsHistoryTable(Configuration.GetConnectionString("AppMigrationTable"), Configuration.GetConnectionString("AppSchema")));
options.DefaultSchema = Configuration.GetConnectionString("AppSchema");
})
It have actually worked, but I wonder if is there an official or simpler way to solve this problem.

You've done it in the correct way as there is no out of the box option to control the column name mapping since it is hardcoded.
There only exists an option to control the tablename mapping:
var builder = services
.AddIdentityServer(options =>
{
...
})
.AddConfigurationStore<CustomConfigurationDbContext>(options =>
{
...
options.ApiResource = new TableConfiguration("MyTableName", "MySchemaName");
})

Related

How to log database table value changes programmatically in terms of auditing?

May be this could be accomplished using a trigger and an audit log table in SQL server. Or perhaps it could be accomplished by overriding the SaveChanges() method in Entity Framework. My concern is how to write the code to get it done and which one will be efficient. Can anybody help me?
If you log changes through the code, alongside data, you can add additional information to the audit log such as IP, user info, client info, ... and it is really helpful. The downside would be if someone changes data directly via the database you cannot find out what data has changed and the performance of logging data change by audit log table is better. If you just need to capture data change and don't need to find out who and from where data has changed choose the database approach.
Here is an implementation to capture data changes by EF Core:
public interface IAuditableEntity
{
}
public class AuditLogEntity
{
public int Id { get; set; }
public string UserName { get; set; }
public DateTime CreateDate { get; set; }
public ChangeType ChangeType { get; set; }
public string EntityId { get; set; }
public string EntityName { get; set; }
public IEnumerable<EntityPropertyChange> Changes { get; set; }
}
public class EntityPropertyChange
{
public string PropertyName { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
}
public enum ChangeType
{
Add = 1,
Edit = 2,
Remove = 3
}
In DbContext:
public class RegistryDbContext : DbContext
{
private readonly IHttpContextAccessor _contextAccessor;
public RegistryDbContext(DbContextOptions<RegistryDbContext> options, IHttpContextAccessor contextAccessor) :
base(options)
{
_contextAccessor = contextAccessor;
}
public DbSet<AuditLogEntity> AuditLogs { get; set; }
public override int SaveChanges()
{
CaptureChanges();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
CaptureChanges();
return await base.SaveChangesAsync(cancellationToken);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AuditLogEntity>().Property(p => p.UserId).IsUnicode(false).HasMaxLength(36).IsRequired(false);
modelBuilder.Entity<AuditLogEntity>().Property(p => p.EntityId).IsUnicode(false).HasMaxLength(36).IsRequired();
modelBuilder.Entity<AuditLogEntity>().Property(p => p.EntityName).IsUnicode(false).HasMaxLength(256).IsRequired(false);
builder.Ignore(p => p.Changes);
modelBuilder.Entity<AuditLogEntity>()
.Property(p => p.Changes).IsUnicode().HasMaxLength(int.MaxValue).IsRequired(false)
.HasConversion(
changes => JsonConvert.SerializeObject(changes, Formatting.None, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Include,
TypeNameHandling = TypeNameHandling.Auto
}),
changes => JsonConvert.DeserializeObject<IList<EntityPropertyChange>>(changes, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Include,
TypeNameHandling = TypeNameHandling.Auto
}));
}
private void CaptureChanges()
{
var changes = ChangeTracker
.Entries<IAuditableEntity>()
.Where(e =>
e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted)
.Select(GetAuditLogItems)
.ToList();
AuditLogs.AddRange(changes);
}
private AuditLogEntity GetAuditLogItems(EntityEntry entry)
{
var auditEntity = new AuditLogEntity
{
CreateDate = DateTime.Now,
EntityId = entry.Properties.FirstOrDefault(f => f.Metadata.IsPrimaryKey())?.CurrentValue?.ToString(),
EntityName = entry.Metadata.Name,
UserName = _contextAccessor?.HttpContext?.User?.Name.ToString(),
};
switch (entry.State)
{
case EntityState.Added:
auditEntity.ChangeType = ChangeType.Add;
auditEntity.Changes = GetChanges(entry.Properties, e => true).ToList();
foreach (var entityChange in auditEntity.Changes)
entityChange.OldValue = null;
break;
case EntityState.Modified:
auditEntity.ChangeType = ChangeType.Edit;
auditEntity.Changes = GetChanges(entry.Properties, e => e.IsModified).ToList();
break;
case EntityState.Deleted:
auditEntity.ChangeType = ChangeType.Remove;
break;
}
return auditEntity;
}
private IEnumerable<EntityPropertyChange> GetChanges(IEnumerable<PropertyEntry> properties,
Func<PropertyEntry, bool> predicate) => properties
.Where(predicate)
.Select(property =>
new EntityPropertyChange
{
PropertyName = property.Metadata.Name,
OldValue = property.OriginalValue?.ToString(),
NewValue = property.CurrentValue?.ToString()
});
}
I use IAuditableEntity (an empty interface) to mark entities that I want to capture changes.
public class CustomerEntity : IAuditableEntity
{
...
}
You can also use Audit.NET library to capture changes.

asp mvc code first update model without lossing data

I have the following DbContext:
namespace Tasks.Models
{
public class TaskDBInitializer : DropCreateDatabaseIfModelChanges<TasksContext>
{
protected override void Seed(TasksContext context)
{
var projects = new List<Projects>
{
new Projects{ Title="proTitle", Describe="proDescribe" },
};
projects.ForEach(p => context.Projects.Add(p));
context.SaveChanges();
base.Seed(context);;
}
}
public class TasksContext : DbContext
{
public TasksContext() : base("name=TaskDB")
{
Database.SetInitializer(new TaskDBInitializer());
}
public DbSet<Task> Task { get; set; }
public DbSet<Projects> Projects { set; get; }
}
}
I now want to add another model but don't want to lose data that exists within the current database.
How can I add a model to my DbContext without losing data?
Instead of using DropCreateDatabaseIfModelChanges<TContext> as your IDatabaseInitializer<TContext> use MigrateDatabaseToLatestVersion<TContext,TMigrationsConfiguration> which will determine changes within your DbContext then update your existing database to be compatible.
Here is an example of how to implement the MigrateDatabaseToLatestVersion initializer:
namespace Tasks.Models
{
public sealed class TaskDBConfiguration : DbMigrationsConfiguration<TasksContext>
{
public TaskDBConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
}
protected override void Seed(TasksContext context)
{
var projects = new List<Projects>
{
new Projects { Title = "proTitle", Describe = "proDescribe" },
};
projects.ForEach(p => context.Projects.Add(p));
context.SaveChanges();
base.Seed(context);
}
}
public class TasksContext : DbContext
{
public TasksContext() : base("name=TaskDB")
{
Database.SetInitializer<TasksContext>(
new MigrateDatabaseToLatestVersion<TasksContext, TaskDBConfiguration>()
);
}
public DbSet<Task> Task { get; set; }
public DbSet<Projects> Projects { set; get; }
}
}

Asp.net core Tables for Identity not being created in the database

When I see at other sample projects, the number of tables created for supporting Identity in the db is great (such ones as AspNetRoles, AspNetUserClaims, etc..), but in my case when I make the migration and the update only the User table has been created. What is the reason?
Here is my code in the startup, in the dbcontext and my class user:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSession();
services.AddMemoryCache();
services.AddDbContext<ApplicationDbContext>( options =>
options.UseSqlServer(Configuration["Data:photoarchiver:ConnString"]));
services.AddIdentity<User, IdentityRole > (
opts => {
opts.Password.RequireDigit = false;
opts.Password.RequiredLength = 7;
opts.Password.RequireLowercase = true;
opts.Password.RequireUppercase = false;
opts.Password.RequireNonAlphanumeric = false;
}).AddEntityFrameworkStores<ApplicationDbContext>();
}
Class DbContext:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<User>().HasMany(u => u.Photos).WithOne(i => i.User);
modelBuilder.Entity<Photo>().ToTable("Photos");
modelBuilder.Entity<Photo>().HasOne(i => i.User).WithMany(u => u.Photos);
modelBuilder.Entity<Category>().ToTable("Categories");
}
public DbSet<Photo> Photos { get; set; }
public DbSet<Category> Categories { get; set; }
}
Class User:
public class User : IdentityUser
{
public virtual List<Photo> Photos { get; set; }
[Required]
public string DisplayName { get; set; }
public string Notes { get; set; }
[Required]
public DateTime CreatedDate { get; set; }
}
To get all the AspNetRoles, etc tables "for free" you need to change your ApplicationDbContext to extend from IdentityDbContext<User> instead of just DbContext. IdentityDbContext<T> is found in the Microsoft.AspNetCore.Identity.EntityFrameworkCore namespace. You can see from the source code https://github.com/aspnet/Identity/blob/master/src/EF/IdentityDbContext.cs, IdentityDbContext will bring in the required DbSetproperties.
As you correctly identified in the comment to your question, you will need to call base.OnModelCreating(builder) and re-make your migration files.

Insert statement conflicts with FK constraint on ApplicationUsers

I've got an Entity as follows:
public class EntityX {
public int Id { get; set; }
...
[ForeignKey("Scheduled By")]
public string ScheduledById { get; set; }
public virtual ApplicationUser ScheduledBy { get; set; }
}
When I try to insert a value into the table, I get the following error:
"The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_dbo.EntityX_dbo.ApplicationUsers_ScheduledById". The conflict
occurred in database "DB", table "dbo.ApplicationUsers", column 'Id'.
The statement has been terminated."
The first thing that comes to mind is that the ApplicationUser table is empty because the IdentityUser table (AspNetUsers) holds all the values. However, its TPH and has a Discriminator column populated with the ApplicationUser table name.
I've verified that the correct Id is being populated when sent in the DB (i.e. it corresponds to an actual User ID) but can't figure out why this is happening.
Thank you in advance. Cheers!
UPDATE:
The space in "Scheduled By" was a typo. It was copied over incorrectly. The actual code has it written as pointed out "ScheduledBy".
UPDATE 2:
The problem it seems lies in the contexts somewhere. I've got two, one DataContext that extends from DbContext as follows:
public class DataContext : DbContext
{
public DbSet<EntityX> EntityXs { get; set; }
...
}
static DataContext()
{
Database.SetInitializer<DataContext> (new CreateInitializer ());
}
public DataContext()
: base("DataContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
...
}
And another, extending from IdentityDbContext as follows:
public class SecurityContext : IdentityDbContext<ApplicationUser>
{
static SecurityContext()
{
Database.SetInitializer<SecurityContext> (new CreateInitializer ());
}
public SecurityContext()
: base("SecurityContext")
{
Database.Initialize(force: true);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUser>()
.ToTable("AspNetUsers");
modelBuilder.Entity<ApplicationUser>()
.ToTable("AspNetUsers");
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
base.OnModelCreating(modelBuilder);
}
With this, the configuration works... but I'm presented with an issue where I've got an extra IdentityRole_Id appearing in the AspNetUserRoles table as described at this post: EF Code First Migration unwanted column IdentityRole_Id. To work around that issue, I followed Hao Kung's advice here: Create ASP.NET Identity tables using SQL script and changed my contexts' OnModelCreating methods this way:
DataContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
}
And SecurityContext...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var user = modelBuilder.Entity<IdentityUser>()
.ToTable("AspNetUsers");
user.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId);
user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId);
user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
user.Property(u => u.UserName).IsRequired();
modelBuilder.Entity<ApplicationUser>().ToTable("AspNetUsers"); //Needed?
modelBuilder.Entity<IdentityUserRole>()
.HasKey(r => new { r.UserId, r.RoleId })
.ToTable("AspNetUserRoles");
modelBuilder.Entity<IdentityUserLogin>()
.HasKey(l => new { l.UserId, l.LoginProvider, l.ProviderKey })
.ToTable("AspNetUserLogins");
modelBuilder.Entity<IdentityUserClaim>()
.ToTable("AspNetUserClaims");
var role = modelBuilder.Entity<IdentityRole>()
.ToTable("AspNetRoles");
role.Property(r => r.Name).IsRequired();
role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId);
}
Although doing this builds the DB correctly, with a Discriminator column in the AspNetUsers table populating with "ApplicationUser" as the value and without the extra columns in AspNetUserRoles, any attempt at inserting the user's Id value into EntityX as FK fails.
I'm completely lost.
So, it turns out that there wasn't any problem with how the entity was set up. It was due to some issue that was arising when migrating to the same DB via the two contexts. Merging them into one fixed the issue. I've posted how I resolved things below. Hopefully this saves someone else time and torment.
public class SecurityContextContext : IdentityDbContext<ApplicationUser>
{
public DbSet<EntityX> EntityX { get; set; }
...
static SecurityContext()
{
Database.SetInitializer<SecurityContext> (new CreateInitializer());
}
public SecurityContext()
: base("SecurityContext")
{
Database.Initialize(force: true);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var user = modelBuilder.Entity<IdentityUser>()
.ToTable("AspNetUsers");
user.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId);
user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId);
user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
user.Property(u => u.UserName).IsRequired();
user.HasKey(u => u.Id);
var appUser = modelBuilder.Entity<ApplicationUser>().ToTable("AspNetUsers"); //Needed?
appUser.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId);
appUser.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId);
appUser.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
appUser.Property(u => u.UserName).IsRequired();
modelBuilder.Entity<IdentityUserRole>()
.HasKey(r => new { r.UserId, r.RoleId })
.ToTable("AspNetUserRoles");
modelBuilder.Entity<IdentityUserLogin>()
.HasKey(l => new { l.UserId, l.LoginProvider, l.ProviderKey })
.ToTable("AspNetUserLogins");
modelBuilder.Entity<IdentityUserClaim>()
.ToTable("AspNetUserClaims");
var role = modelBuilder.Entity<IdentityRole>()
.ToTable("AspNetRoles");
role.Property(r => r.Name).IsRequired();
role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId);
}
This configuration works for me. Although the ApplicationUser table does not generate via this mapping, there is still a Discriminator column created in the AspNetUsers table filled with "ApplicationUser". The AspNetUsers table also gets the extra fields that I defined in the ApplicationUser class. The IdentityRole_Id is eliminated and I'm able to assign roles and get them successfully. The FK issue is also resolved. Everything works as intended.
As the error message indicates, your foreign key needs to be associated with a valid entity property. If you place the ForeignKey attribute on a foreign key property the string parameter represents the name of the associated navigation property. Remove the whitespace to match the name of the navegation property:
public class EntityX
{
public int Id { get; set; }
...
[ForeignKey("ScheduledBy")]
public string ScheduledById { get; set; }
public virtual ApplicationUser ScheduledBy { get; set; }
}
use this:
public class EntityX
{
public int Id { get; set; }
[ForeignKey("ScheduledBy")]
public string ScheduledById { get; set; }
[ForeignKey("ScheduledById")]
[InverseProperty("EntityX_1")]
public virtual ApplicationUser ScheduledBy{ get; set; }
}
public class ApplicationUser
{
public string ScheduledById { get; set; }
.
.
.
[InverseProperty("ScheduledBy")]
public virtual ICollection<EntityX> EntityX_1{ get; set; }
}

Fluent Nhibernate and HasOne() problems

How do you do a 1 to 1 relationship with fluent nhibernate? I am using ms sql server 2008 and every time I looked the db tables through the database diagram viewer the table that should have one to one relationships don't seem to have them.
Users
UserId <pk> Guid
Settings
UserId <pk> Guid
public Settings
{
public virtual Guid UserId {get; private set;}
public virtual Setting User { get; set; }
}
public User
{
public virtual Guid UserId {get; private set;}
public virtual Setting Setting { get; set; }
}
public class UserMap : ClassMap<User>
{
Id(x => x.UserId);
HasOne(x => x.Setting);
}
public class SettingMap : ClassMap<Setting>
{
Id(x => x.UserId);
HasOne(x => x.User);
}
So I tried this but it did not work.
Why do you have a property of type Setting on your Setting class? It's called User so I would expect it (also because of your description of the problem) that it be a User class instead.
I had a similar problem, couldnt see any relationships being generated. I finally found that this worked:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Id(x => x.Id);
HasOne(s => s.Child).Cascade.All();
}
}
public class ChildMap : ClassMap<Model.Child>
{
public ChildMap()
{
Id(x => x.Id);
HasOne(s => s.Parent).Constrained().ForeignKey();
}
}

Resources