When a role is removed from a user, I need to track who did it (ie. which AbpUser) and when they did it.
The obvious solution is to redefine the UserRole entity so that it inherits from FullAuditedEntity instead of CreationAuditedEntity, but the UserRole entity is defined in a nuget package so I cannot simply change the definition.
Is there a way to achieve this behavior that I am not seeing?
Here is what I have tried so far.
Approach 1: I tried handling this at the database level by setting up a delete trigger on the AbpUserRole table which would insert a record into a AbpUserRoleDeleted table, but I can't think of a way to find out which AbpUser made the deletion with this approach. I can only track when the action happened.
Approach 2: I tried listening for the EntityDeleted domain event on UserRole entities, but it does not seem to get triggered. Interestingly, the EntityUpdated event is triggered when I remove a role from a user, but even assuming that this event would only ever be triggered when a UserRole is deleted, the event data still does not include who made the deletion. If it did, I could manually save the audit information in a separate table just like a database delete trigger would, but this time I would have the AbpUser that was responsible for the deletion.
Approach 3: I tried extending the UserRole entity by following the steps here. I was able to implement the IDeletionAudited interface and generate a migration that creates the associated columns on the AbpUserRoles table, but removing a role from a user performs a hard delete instead of a soft delete so I can't tell if the columns even get populated. I am assuming they do not.
Approach 4: I tried enabling Entity History for the UserRole entity, but it seems to only track when a UserRole entity is created.
This seems to work fine.
//src\aspnet-core\src\Company.App.EntityFrameworkCore\EntityFrameworkCore\AppDbContext.cs
namespace Company.App.EntityFrameworkCore
{
public class AppDbContext : AbpZeroDbContext<Tenant, Role, User, AppDbContext>, IAbpPersistedGrantDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
ChangeTracker.StateChanged += OnEntityStateChanged;
}
private void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
if (e.Entry.Entity is UserRole && e.NewState == EntityState.Deleted)
{
//update instead of delete
e.Entry.State = EntityState.Modified;
e.Entry.CurrentValues["IsDeleted"] = true;
e.Entry.CurrentValues["DeletionTime"] = DateTime.Now;
e.Entry.CurrentValues["DeleterUserId"] = AbpSession.UserId;
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//use query filter on the `IsDeleted` shadow property
modelBuilder.Entity<UserRole>().HasQueryFilter(p => !EF.Property<bool>(p, "IsDeleted"));
modelBuilder.Entity<UserRole>().Property<bool>("IsDeleted");
modelBuilder.Entity<UserRole>().Property<DateTime?>("DeletionTime").IsRequired(false);
modelBuilder.Entity<UserRole>().Property<long?>("DeleterUserId").IsRequired(false);
}
}
}
Related
I feel like I'm missing something. Authorize by itself works, but using roles does not. The cshtml-Code contains razor stuff, which I'm not sure I'm allowed to use in combination with roles based authorization. Furthermore, IsInRole always returns false.
In my database in the table AspNetUserRoles, the role instance is there with the correct RoleId and UserId. I added the user to the role in the Seed method of the database.
if (!userManager.IsInRole(adminUser.Id, "SystemAdministrator"))
userManager.AddToRole(adminUser.Id, "SystemAdministrator");
Do I need to add them somewhere else, like in some kind of role manager?
Here's what I think is the relevant part of the Configuration in Startup:
app.CreatePerOwinContext<RoleManager<AppRole>>((options, context) =>
new RoleManager<AppRole>(
new RoleStore<AppRole>(context.Get<MyANTon.DataContext.AntContext>())));
Maybe, after this, I have to add the user to the role?
Edit: I found a possible error source for this. My db context does not seem to include the identity tables like AspNetUserRoles or even AspNetUsers. I migrated from Forms-Authentication to Identities last week, this is probably the problem now. Do I have to change the context accordingly? It inherits from IdentityDbContext<AppUser>, which is why I can't just add the AspUserstuff (since it's already there), but when I look at the context at runtime, it's not there...
Next edit: I was missing the web.config for my role manager. After adding it, it seems like my data context idea actually feels to be true. Now, the error thrown is: 'The entity type AppUser is not part of the model for the current context.'. My context inherits from IdentityDbContext<AppUser>, why doesn't it contain AppUser then?
Context class:
public class AntContext : IdentityDbContext<AppUser>
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<AntContext>(null);
modelBuilder.Entity<AppUser>().ToTable("AppUsers");
base.OnModelCreating(modelBuilder);
}
UserManager constructor call in controller:
private UserManager<AppUser> userManager = new UserManager<AppUser>(new UserStore<AppUser>());
When you are creating your roles ... make sure there are no extra spaces before or after the Role Name when you save them into the DB. I wasted 3/4 of a day trying to chase down why this was happening.
Its looks like you have "SystemAdministrator" role and may be many others.
You should use [Authorize(Roles = "RoleName")]
ex:- [Authorize(Roles = "SystemAdministrator")]
Cross verify in your database if you have the corresponding roles.
I have two WebApp.
Both refer to the same database, and the need to organize the migration of both App.
Both application setup to MigrateDatabaseToLatestVersion.
When you start with a blank database application first comes the creation of scheme, in table __MigrationHistory create record about this, and everything is OK.
But when you start the second application should make changes to existing database tables, but the application crashes with an error - "... This table already exists"
How to solve this problem?
Example of code second app
Global.asax
Database.SetInitializer(new SyncContextInitializer());
using (var context = new SyncDataContext())
{
context.Database.Initialize(force: true);
}
public class SyncContextInitializer : MigrateDatabaseToLatestVersion<SyncDataContext, SyncConfiguration>
{ }
public class SyncDataContext : DataContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new IdentityUserLoginMap());
.......
base.OnModelCreating(modelBuilder);
}
}
public sealed class SyncConfiguration : DbMigrationsConfiguration<SyncDataContext>
{
private readonly bool _pendingMigrations;
public SyncConfiguration()
{
AutomaticMigrationDataLossAllowed = true;
AutomaticMigrationsEnabled = true;
var migrator = new DbMigrator(this);
_pendingMigrations = migrator.GetDatabaseMigrations().Any();
}
......
.....
}
That's not going to work. When you modify the model in one app, it looks at the prior migration (in code) to compare. It will try to recreate the changes made in the other app or you will get a model mismatch error.
The best solution is to maintain the models in one application. Otherwise you would have to create a migration in the other application to keep it in sync:
// Application 1:
add-migration MyChanges
update-database
// Application 2:
add-migration SyncMyChanges -IgnoreChanges // Just update meta model
update-database
You would need to take care that there were no pending changes in App 2. See Entity Framework Under the Hood
If the models are not shared, you may just need to create an initial migration starting point in both apps:
add-migration Initial -IgnoreChanges
I just created my first MVC Website.
To avoid having to program the controller myself, I am using the Individual User Accounts template from Microsoft.
I know, that this template uses the Entity Framework to create an express database to persist the user/account data.
Since I already have a database, which I want to use, I want to change the
template so it uses the DbContext for said database.
I was able to change the connectionString, so that the tables of the template got created in my database. But I don't want it to create it's own tables but use my already created tables.
Is there any easy way to achieve this?
Or should I just write the whole account/user controller from scratch myself?
// This is an example of DbContext class it implements DbContext
// If you do not use constructor(s) then the expectation by entity framework
// will be that your name of your connectionstring in web.config
// or app.config is name name as your class so e.g. "YourContext",
// otherwise "Name="YourConnectionString"
public class YourContext : DbContext
{
// constructor as you wish /want
public YourContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{ }
// critical mapping
public DbSet<someModel> someModel { get; set; }
// critical overide
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// critical key to NOT let your database get dropped or created etc...
Database.SetInitializer<YourContext>(null);
// This is an example of mapping model to table
// and also showing use of a schema ( dbo or another )
modelBuilder.Entity<someModel>().ToTable("someTable", schemaName: "dbo");
}
}
I am working on a site where we'd like schema to be the differentiator between projects and in doing so, securing things up between projects.
I have had some luck with this using the HasDefaultSchema method in OnModelCreating in my data context, but also having to make my DbContext implement IDbModelCacheKeyProvider and implementing the CacheKey property. It has worked before. Unfortunately, this solution seems to be inconsistent and I am currently having a problem where I am trying to update my model but getting the following error when running Update-Database:
The specified schema name "dbo" either does not exist or you do not have permission to use it.
The connection string has a user that only has access to my xma schema, so this error makes sense if I had changed the schema, but you can see in the following code, I haven't:
public class DataContext : DbContext, DbModelCacheKeyProvider
{
public DataContext()
//: base("name=DataContext")
: base("DataContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.HasDefaultSchema("xma");
base.OnModelCreating(modelBuilder);
}
public virtual DbSet<Article> Articles { get; set; }
public virtual DbSet<Author> Authors { get; set; }
...
public string CacheKey
{
get { return Utility.SchemaPrefix ?? "xma"; }
}
}
This problem occurs with other team members too, so any help would be much appreciated.
Thanks
Edit: I forgot to mention, this appears to be a problem on a clean database.
I had removed the pending changes to the model, and was able to run update-database -script as advised by Steve, turns out that the initial migrations are based on the dbo schema so changed to a more privileged user and was able to continue with recreating the database. My mistake unfortunately.
I am trying to setup automatic migration updates using the IdentityDbContext class and propagating changes to the actual DbContext for the entire database.
Before I get into the code, on my implementation of the IdentityDbContext with automatic migrations I get this error:
Automatic migrations that affect the location of the migrations history system table (such as default schema changes) are not
supported. Please use code-based migrations for operations that affect
the location of the migrations history system table.
I am not going to post the models that are associated with the migrations and context code unless someone finds them of use.
Implemenation of the IdentityDbContext:
public class SecurityContext: IdentityDbContext<User>
{
public SecurityContext() : base("MyActualContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//removing this line I do not get an error, but everything gets placed into the dbo schema. Keeping this line, i get the above error.
modelBuilder.HasDefaultSchema("ft");
}
}
So I tried adding this class to place the migrations history into the correct schema. This, in fact, does move the migrations history into the correct schema, but everything else remains in the dbo schema.
public class MyHistoryContext : HistoryContext
{
public MyHistoryContext(DbConnection dbConnection, string defaultSchema)
: base(dbConnection, defaultSchema)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("ft");
}
}
public class SecurityContextMigrations : DbMigrationsConfiguration<SecurityContext>
{
public SecurityContextMigrations()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
//When the migrations get set, I use the new class created to move the migrations to the correct schema.
SetHistoryContextFactory("System.Data.SqlClient", (c, s) => new MyHistoryContext(c, s));
}
protected override void Seed(SecurityContext context)
{
...
}
}
Ideally, I'd like everything to be in the ft schema. I don't think the migrations are that complex that I need to manually setup the migrations. I was hoping for simplicity sake, I could use automatic migrations for this. I am wondering if this approach is impossible and what I need to do to make this happen and any changes made to the models do get propagated.
I have a similar issue with Oracle 12c and EF6: I cannot get automatic migrations to work. I found, however, the following partial success factors: - I needed to set
modelBuilder.HasDefaultSchema("")
on my DbContext in order to get the runtime see the tables in the logon schema of the particular user - For the update-database it was necessary to set the MyHistoryContext parameters like that:
public class MyHistoryContext : HistoryContext
{
public MyHistoryContext(DbConnection dbConnection, string defaultSchema)
: base(dbConnection, defaultSchema)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("L2SRVRIZ");
}
}
NOTE: You need to hard-code the schema name there. In this way, update-database does not try to use dbo as schema (but still no automatic migrations are possible, they will drop your MigrationHistory table and mess up everything). This is in my opinion a nasty bug inside either EF6 or the Oracle custom class. As I have no maintenance contract with them, I cannot file a ticket.
For your case, I think its not possible somehow by design to avoid the error message with automatic migrations. EF6 thinks, for some reason, that if you use a custom schema name, that you actually moving the __MigrationHistory table from the default dbo schema, which is of course not true.
Or, did you find a solution for that?
BR Florian