Get user with assigned roles - asp.net-core

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();

Related

ASP.NET Core 3.1 Web API: how to protect sensitive data from return with model?

I have a Posts model class that has a relation with Users model.
When I call API to get Posts with the owner of it (user), it returns with all user info including password.
How to prevent model from returning sensitive information like passwords?
You should create new classes that you return from your actions. Only include the fields/information you want to return.
These classes are also known as Data Transfer Objects (DTO).
You can use [JsonIgnore] to avoid serializing the property value:
public class Users
{
public int Id { get; set; }
[System.Text.Json.Serialization.JsonIgnore]
public string Password{ get; set; }
//...
}

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");
}

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!

code first membership provider

How can I integrate EF 5.0 with membership provider using code first?
I have my own database schema which I want to use for registration of users etc.
You should take a look at the SimpleMembershipProvider
It is very easy to use it together with EF.
Update
For MVC4 I would start with the blank template.
you need WebSecurity.InitializeDatabaseConnection to set up the database.
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "Users", "Id", "UserName", true);
It takes as parameters a name of a connectionstring, the table, a unique identifier column and the username-column.
The model I use for this piece of code above is:
[Table("Users")]
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string UserName { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
To log someone in:
WebSecurity.Login(aUsername, aPassword);
The method above returns a bool, if it is true, login was successfull.
In the Web.config you do not need to define a membershipProvider as it was with default ASP.NET Membership.
If you need to gain access to the provider (to delete an account):
var provider = (SimpleMembershipProvider) Membership.Provider;
provider.DeleteAccount(aUsername); // Delete the account
provider.DeleteUser(aUsername, true); // delete the user and its data
For creating a new user (with my given model in this case)
WebSecurity.CreateUserAndAccount(aUsername, aPassword, new { Email = aEmail, FirstName = aFirstName, LastName = aLastName });
Benefit is, that you can now use your model for other EF classes as a foreign key without having the hassle when you want to do this with the normal asp.net membership. :-)
http://msdn.microsoft.com/en-us/library/f1kyba5e
This is actually nothing to do with EF.
EF is just a way to read data.
See also FORMS Authentication
http://support.microsoft.com/kb/301240?wa=wsignin1.0

"Indirect" ManyToMany Relation

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