"Indirect" ManyToMany Relation - fluent-nhibernate

Warning: I am brand new to NHibernate!
I have a User object. This User object has related Roles. These Roles have related Privileges. The goal is to get a list of Privileges for a User. Privileges are "indirectly" related to Users through Roles.
When doing a select on the user to get the list of related Privileges I might get back multiples of a Privilege. So I do a UNIQE select.
I am wondering if it is possible to have some 'magic' NHibernate mapping that will fill my Roles list AND my Privilege list. The easy part I solved myself (proud! ;-) ) is mapping correctly the Roles. I have no idea how to get the 'indirectly' related (unique) Privileges.
Any ideas?

public class User
{
public virtual int Id { get; set; }
public virtual ICollection<Role> Roles { get; private set; }
public virtual IEnumerable<Privilege> Privileges
{
get { return Roles.SelectMany(role => role.Priveleges).Distinct(); }
}
public User()
{
Roles = new List<CompanyRole>();
}
}
and query like
session.QueryOver<User>()
.Fetch(u => u.Roles).Eager
.Fetch(u => u.Roles.Privileges).Eager

Related

User.IsInRole returns false after switching to one role per user

After switching from default AspNetRole many-to-many relation to one role per user, and created a custom identity role, User.IsInRole("Admin") is always returning false.
The user signed in is seeded in the DB context, while i deleted and seeded the user again to match one-role per user.
Heres the app user:
public class ApplicationUser : IdentityUser
{
// person props
public string RoleId { get; set; }
public ApplicationIdentityRole Role { get; set; }
}
And the identity:
public class ApplicationIdentityRole : IdentityRole<string>
{
public List<ApplicationUser> ApplicationUser { get; set; }
}
Here is my DBContext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationIdentityRole, string>
{
// DBSets..
protected override void OnModelCreating(ModelBuilder builder)
{
string ADMIN_ID = _config.GetValue<string>("Tokens:SysUsers:Admin");
string ADMIN_ROLE_ID = _config.GetValue<string>("Tokens:Roles:Admin");
builder.Entity<ApplicationIdentityRole>().HasData(new ApplicationIdentityRole
{
Id = ADMIN_ROLE_ID,
Name = "Admin",
NormalizedName = "ADMIN"
});
var hasher = new PasswordHasher<ApplicationUser>();
builder.Entity<ApplicationUser>().HasData(new ApplicationUser
{
Id = ADMIN_ID,
UserName = "admin",
NormalizedUserName = "ADMIN",
Email = "admin#sys.com",
NormalizedEmail = "ADMIN#SYS.COM",
EmailConfirmed = true,
PasswordHash = hasher.HashPassword(null, "123123"),
SecurityStamp = string.Empty,
Title = "Admin",
RoleId = ADMIN_ROLE_ID
});
}
}
And i have changed the startup to match the app identity role class:
services.AddIdentity<ApplicationUser, ApplicationIdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddDefaultUI();
services.AddTransient<UserManager<ApplicationUser>>();
Login:
// sigin manager
private readonly SignInManager<ApplicationUser> _signInManager;
// usage
var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
Authorization is working perfectly, but the role is always returning false.
I realized that inheriting from IdentityDbContext<ApplicationUser, ApplicationIdentityRole, string> generates the default roles table (many-to-many) so theoretically, it will consider the added Role property to the user just a dataset AspNetRole and validate the many-to-many table not the RoleId.
I couldn't find any documentation that covers how to set one-role per user using ef.
Any help is really appreciated!
Update: on successful sign-in, the role is not added to the claims by the SignInManager but only added when there is a record in the AspNetUserRole (many-to-many) table which I want to exclude.
Authorization is working perfectly, but the role is always returning
false.
That's because the sign-in process will look for role data through the AspNetUserRole table, and you did not insert any data in that table.
You can extend the Identity models, but you cannot modify the relationships among them. To find the options you have to customize Identity models, take a look at Identity model customization in ASP.NET Core
The changes you've made will not affect the default many-to-many relationship between User and Role (take a look at your database, and you'll still find the joining table AspNetUserRole). And the built-in functionalities of UserManager, RoleManager, SignInManager etc will continue to work based on that default relationship.
For your current scenario:
With your custom relationship, you have to use custom approach to access Role data. Instead of User.IsInRole("Admin") you have to do something like -
var user = context.ApplicationUsers.Include(p=> p.ApplicationIdentityRole).FirstOrDefault(p=> p.UserName == User.Identity.Name);
var isInRole = user.ApplicationIdentityRole.Name == "Admin";
And if you want one-role per user, there's no need to create the extra relation and increase the complexity. You can simply add a property named Role in your ApplicationUser model -
public class ApplicationUser : IdentityUser
{
public string Role { get; set; }
}
This way you still have to use your own approach to access Role data (since you are not using the many-to-many relationship), but it will be easier now -
var isInRole = context.ApplicationUsers.FirstOrDefault(p=> p.UserName == User.Identity.Name && p=> p.Role == "Admin");
For a better solution:
For now, you are creating User and Role via data-seeding. But normally you would want to add new Role to a User with something like -
await userManager.AddToRoleAsync(user, "Admin");
and this would put Role linking data in the AspNetUserRole table and that data will be used by the sign-in process, and you would have expected result from User.IsInRole("Admin") method.
So, if you really want to restrict the Users to have only one Role, while everything to work as expected, you just need make sure that more than one role cannot be added to a User. You can do that by -
Deriving from UserManager<ApplicationUser> class and then overriding the AddToRoleAsync method. But for you purpose, it will be an overkill.
Doing the following -
if ((await userManager.GetRolesAsync(user)).Count == 0)
{
await userManager.AddToRoleAsync(user, "Admin");
}

Get user with assigned roles

Using ASP.NET Core identity, I create a new user with UserManager.CreateAsync() and assign them to an existing role with UserManager.AddToRoleAsync. This works, as the realtion between user and role is stored in the AspNetUserRoles table of the database.
But when I fetch the user using the UserManager (e.g. UserManager.FindByMail() method) then the Role list is empty. I also tried the Include function from EF like this:
var user = userManager.Users.Include(u => u.Roles).FirstOrDefault(u => u.Email == "test#test.de");
This gave me the Ids of the n:m association table, which is not very usefull as I need the role names. Loading them using a second query is also not possible, since the Roles attribute of the identity user is readonly. I would expect to get a List<string> of the role-names. Couldn't find any information about this.
For me the only workaround seems to add a custom attribute to my user and fill them with the data, which I fetch using a second query. But thats not a nice solution. Cant belive that ASP.NET Core Identity has no better way of getting those data...
UserManager does not load the relations by default.
The manual inclusion is a good way but as stated here - direct M:N relationships are not yet supported by EntityFramework Core.
So there are two ways I see:
(The preffered one) Use
userManager.GetRolesAsync(user);
This will return a List<string> with user's role names. Or use some EF query to get an IdentityRole objects by joined IdentityUserRole. Unfortunatelly, this requires an acceptance with the fact the roles will not be directly in the User entity.
OR you can implement custom IdentityUserRole, create a relation to IdentityRole there and then query it with `
Include(user => user.Roles).ThenInclude(role => role.Role)
How to implement own Identity entities is described e.g. here. But it's complicated approach and the Role objects will be nested in the binding entities.
However, you can declare a property in your ApplicationUser:
[NotMapped]
public List<string> RoleNames {get; set;}
and use it at you free will...
Here is the solution I came up with. It may not be the most efficient. Hope it helps someone.
public class UserVM
{
public string Id { get; set; }
public string UserName { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Roles { get; set; }
}
// Project each element of query into List<UserVM>
var list = (from user in _userManager.Users
select new UserVM
{
Id = user.Id,
UserName = user.UserName,
Name = user.Name,
Email = user.Email
}).ToList();
list.Select(async user =>
{
var userEntity = await _userManager.FindByIdAsync(user.Id);
user.Roles = string.Join("; ", await _userManager.GetRolesAsync(userEntity));
}).ToList();

Gathering Active Directory Data for MVC intranet application

I need assistance with gathering Active Directory data based on a table in my DB. I have an entity class that holds user requests. Each request has the user's windows name from System.Web.HttpContext.Current.User.Identity.Name. My problem is I cannot figure out how to setup a linq query to associate the AD username to the rest of the AD so I can display their full names instead of their username in my table. Here is what I have so far, any help will be appreciated.
public partial class RequestInfo
{
public int RequestInfoId { get; set; }
public string RequestByUserADId { get; set; }
public System.DateTime RequestDateTime { get; set; }
public string Explanation { get; set; }
public virtual UserInfo UserInfo { get; set; } // where I define my custom roles
}
I can query AD by using the code below. I have tried Get Active Directory User Information With Windows Authentication in MVC 4, but it did not help.
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
using (UserPrincipal user = UserPrincipal.FindByIdentity(context, requestByAdId))
{
return user.DisplayName
}
I may be off here because I am not sure if you are able to successful establish a user principal or not but if you have the user principal you can get property information like the following:
user.GetProperty("propertyName")
Here is a static method that should get you the department for a user, for example.
public static String GetDepartment(UserPrincipal principal)
{
return principal.GetProperty("department");
}
Let me know where this gets you and I can elaborate further if this isn't working.
Edit
It appears you need to go one level deeper to get the fields that aren't by default a part of the user principal. For this you will need to get the directory entry from the user principal first:
DirectoryEntry directoryEntry = (userPrincipal.GetUnderlyingObject() as DirectoryEntry);
Then you need to check if the attribute you are looking for exists, and if it does, get the value. A great way to do this is to create a helper method that you pass your directory entry to along with the string value for the property name that you want to get.
public string GetProperty(DirectoryEntry directoryEntry, string propertyName)
{
if (directoryEntry.Properties.Contains(propertyName))
{
return directoryEntry.Properties[propertyName][0].ToString();
}
else
{
return string.Empty;
}
}
Please note that going to the underlying object is expensive. I believe this operation, by default, is cached for you so subsequent use of this information can be retrieved from cache. Playing around with
directoryEntry.RefreshCache
will get you started with that.
Let me know if this does the trick for you!

NHibernate Parent Child Mapping

I have a parent/child database relationship between the table ACCOUNT and the table USER. Currently I have mapped a bi-directional Fluent mappings like this:
public class Account {
public virtual IList<User> ListUsers { get; private set; }
public virtual void AddUser(User user)
{
user.Account = this;
ListUsers.Add(user);
}
}
MAPPING: HasMany(x => x.ListUsers).Table("UserInfo").KeyColumn("Id_Account").Inverse().Cascade.All().AsList();
public class User {
public virtual Account Account { get; set; }
public string Username { get; set; }
}
MAPPING: References(x => x.Account).Column("Id_Account");
In practice I cannot foresee that I will ever want to reference the Account entity from the User entity. Likewise I cannot foresee my wanting to load all of the User entities from the parent Accounts entity. I am fairly new to NHibernate and was wondering is the above method still the best way to go performance wise? Is a bi-directional relationship preferred and should I look to referencing the Id only? Thanks
bi-directional references is the correct approach, in my opinion. If you use lazy-loading on the Account property, then it would only load the account's id anyway.
you specified that the ListUsers property is inverse=true, meaning the User entity is responsible for saving the reference. Therefore, I believe (if I remember correctly) that the line ListUsers.Add(user); is not necessary, since the association will be created by the User entity.
So this means that you don't have to load the entire ListUsers collection from the db when you add a user.

NHibernate QueryOver distinct

I have this
scenario:
class User
{
Id,
UserName
}
class UserRelationship
{
User GroupUser,
User MemberUser
}
and query
var query = QueryOver.Of<UserRelationship>()
.JoinqueryOver(x=>x.MemberUser)
.Where(x=>x.UserName == "TestUser");
Now I want to return List Distinct User, so I cannot do
TransformUsing(Transformers.DistinctRootEntity)
because this will give me the UserRelationship.
I need something like this:
Select distinct user.ID
from UserRelationship relationship
inner join User user on user.ID = relationship.MemberUser_ID
Please help
thanks
Given the classes:
public class User
{
public virtual int Id {get; set;}
public virtual string UserName {get; set;}
}
public class UserRelationship
{
public virtual int Id {get; set;}
public virtual User GroupUser {get; set;}
public virtual User MemberUser {get; set;}
}
And the fluent mappings of:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x=>x.Id).GeneratedBy.Native();
Map(x=>x.UserName);
}
}
public class UserRelationshipMap : ClassMap<UserRelationship>
{
public UserRelationshipMap(){
Id(x=>x.Id).GeneratedBy.Native();
References(x=>x.GroupUser);
References(x=>x.MemberUser);
}
}
You want to retrieve a list of distinct "User" based on "MemberUser" from the UserRelationship class.
var distinctMemberUsers = QueryOver.Of<UserRelationship>()
.Select(x => x.MemberUser.Id);
var users = QueryOver.Of<User>()
.WithSubquery.WhereProperty(x=>x.Id).In(distinctMemberUsers)
This should use a In clause in the SQL to give you a distinct list of User.
I know this post is old but I just came across the same problem and thought I would share an answer I found to be much simpler.
No matter what - NHibernate will have to query multiple rows for each parent object (unless you use a SubSelect instead of a Join). Because of this, we know we're going to get a list of say, 500 objects, when there are really only 100 unique objects.
Since these objects are already queried, and already in memory - why not use LINQ?
Based on this question: LINQ's Distinct() on a particular property the answer with the most +'s gives a very eloquent solution. Create another list, and have LINQ do the distinct comparison. If we could do distinct at the database it would clearly be the better option - but since that's not an option, LINQ seems to be a good solution.