AutoFac with UserPrincipal and Roles - c#

I have a Custom UserPrincipal and I have an User Entity.
The User Entity holds a collection of Permissions.
I want to make my UserPrincipal aware of those Permissions and all I want to do is to inject the IPrincipal in the construtors I need and then call the IsInRole() method.
How do I achieve that with AutoFac? I'm not finding any reference of how to make the UserPrincipal aware of the User Entity. I could solve that by addidng the permissions to Claims and then get those claims in the IsInRole, but I don't know if that is a good idea?
EDIT:
After some tests I got to this solution:
public interface IUserPrincipal : IPrincipal
{
Guid Id { get; }
Guid? BossId { get; }
string DisplayName { get; }
string Email { get; }
List<string> Permissions { get; }
}
public class UserPrincipal : IUserPrincipal
{
private readonly User _user;
public UserPrincipal(User user, IIdentity identity)
{
_user = user;
Identity = identity;
}
public bool IsInRole(string role)
{
return Permissions.Contains(role);
}
public IIdentity Identity { get; }
public Guid Id => _user.Id;
public Guid? BossId => _user.BossId;
public string DisplayName => _user.Name;
public string Email => _user.Name;
public List<string> Permissions
{
get
{
return _user.Permissions.Select(p => p.Name).ToList();
}
}
}
public User GetCurrentUser()
{
var user = GetUserByEmail(emailAddress);
if (user == null)
{
using (var unitOfWork = _unitOfWorkFactory.CreateUnitOfWork())
{
user = CreateNewUser(unitOfWork, emailAddress, displayName);
}
}
Thread.CurrentPrincipal = new UserPrincipal(user, Thread.CurrentPrincipal.Identity);
return user;
}
And then with AutoFac:
builder.Register(c => new UserPrincipal(c.Resolve<IUserService>().GetCurrentUser(), Thread.CurrentPrincipal.Identity)).As<IPrincipal>().InstancePerRequest();

Related

C# Identity modify custom properties

Hello I Have created custom ApiUser class which inherits from IdentityUser.
public class ApiUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
The question it is set on Creating new user, but how do I change it/update it later?
UserManager<ApiUser> userManager
_userManager does not seem to have any methotds to do so.
[HttpPost]
[Route("UpdateUserDetails")]
public async Task<IActionResult> UpdateUser(ApiUserUpdate apiUserUpdate)
{
var user = await _userManager.FindByEmailAsync(apiUserUpdate.Email);
if(user == null)
{
return BadRequest("No user found with given email");
}
user.FirstName = apiUserUpdate.FirstName;
user.LastName = apiUserUpdate.LastName;
await _userManager.UpdateAsync(user);
return Ok("user updated");
}

How to pass Enum as string parameter to Authorize attribute?

It's my first project to ASP.NET Core Authentication and Authorization and I get this error when I'm trying to pass Enum to [Authorize] attribute :
Error CS1503 Argument 1: cannot convert from
'BasicAuthAPI.Entities.Role' to
'string'
Here is my controller method which gives this error:
[Authorize(Role.Admin)]
[HttpGet]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
Role enum:
public enum Role
{
Admin,
User
}
User Entity:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public Role Role { get; set; }
[JsonIgnore]
public string PasswordHash { get; set; }
}
And the _userService which I have mentioned in controller:
public class UserService : IUserService
{
private DataContext _context;
private IJwtUtils _jwtUtils;
private readonly AppSettings _appSettings;
public UserService(
DataContext context,
IJwtUtils jwtUtils,
IOptions<AppSettings> appSettings)
{
_context = context;
_jwtUtils = jwtUtils;
_appSettings = appSettings.Value;
}
public AuthenticateResponse Authenticate(AuthenticateRequest model)
{
var user = _context.Users.SingleOrDefault(x => x.Username == model.Username);
// validate
if (user == null || !BCryptNet.Verify(model.Password, user.PasswordHash))
throw new AppException("Username or password is incorrect");
// authentication successful so generate jwt token
var jwtToken = _jwtUtils.GenerateJwtToken(user);
return new AuthenticateResponse(user, jwtToken);
}
public IEnumerable<User> GetAll()
{
return _context.Users;
}
public User GetById(int id)
{
var user = _context.Users.Find(id);
if (user == null) throw new KeyNotFoundException("User not found");
return user;
}
}
How can I pass the Admin Role to [Authorize] attribute?
Either use string constants
public static class Role
{
public static string Admin = "Admin";
public static string User = "User";
}
or you can use nameof
[Authorize(nameof(Role.Admin))]
You can just call .ToString()
[Authorize(Role.Admin.ToString())]
[HttpGet]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
Looking at the answer from Alexander I have found the following SO post which highlights the difference between nameof and ToString: What is the difference between MyEnum.Item.ToString() and nameof(MyEnum.Item)?

How to add custom Login and Roles mechanizm to asp mvc 5?

I have to admit. Even after reading quite a few tutorial on this new mvc 5 Identity and all that owin stuff I just can't figure it out.
My task is to implement login and roles listing from Stormpath (Stormpath.com) which is basially a web-store for users and groups. I have created a service that authenticates user & password against stormpath and returns roles/groups assigned to user.
I have also went to the ApplicationSignInManager that is created by default with a new mvc project in Visual Studio and substitutes the body with:
public override Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) {
return Task.Run(() =>
new StormpathService(new Configuration()).AuthenticateUser(userName, password) != null ? SignInStatus.Success : SignInStatus.Failure);
}
The thing passes when user inputs data into login form o a page, but after that the application still thinks that I'm not logged in.
What else need to be done for asp mvc Identity mechanizm to respect custom way of authenticating users and roles management?
This is the minimum I had to make to support logging in from Stormpath.
public class ApplicationUser : IUser {
public string ClientKey { get; set; }
public string Id { get; set; }
public string UserName { get; set; }
public string NewsFilter { get; set; }
public string FullName { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager) {
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
public class StormpathUserStore : IUserStore<ApplicationUser>, IUserRoleStore<ApplicationUser> {
private readonly IStormpathService _stormpathService;
public StormpathUserStore(IStormpathService stormpathService) {
if (stormpathService == null) {
throw new ArgumentNullException("stormpathService");
}
_stormpathService = stormpathService;
}
public Task AddToRoleAsync(ApplicationUser user, string roleName) {
throw new NotImplementedException();
}
public Task RemoveFromRoleAsync(ApplicationUser user, string roleName) {
throw new NotImplementedException();
}
public Task<IList<string>> GetRolesAsync(ApplicationUser user) {
var groups = _stormpathService.GetUserGroups(_stormpathService.GetUserUrlFromId(user.Id));
return Task.FromResult(groups.ToArray() as IList<string>);
}
public Task<bool> IsInRoleAsync(ApplicationUser user, string roleName) {
#if DEBUG
var configuration = ObjectFactory.GetInstance<IConfiguration>();
if (!string.IsNullOrWhiteSpace(configuration.DebugUser)) {
return Task.FromResult(configuration.DebugRoles.Split(',').Contains(roleName));
}
#endif
var isInGroup =
_stormpathService.GetUserGroups(_stormpathService.GetUserUrlFromId(user.Id)).Contains(roleName);
return Task.FromResult(isInGroup);
}
public void Dispose() {
}
public Task CreateAsync(ApplicationUser user) {
throw new NotImplementedException();
}
public Task UpdateAsync(ApplicationUser user) {
throw new NotImplementedException();
}
public Task DeleteAsync(ApplicationUser user) {
throw new NotImplementedException();
}
public Task<ApplicationUser> FindByIdAsync(string userId) {
var userData = _stormpathService.GetUser(_stormpathService.GetUserUrlFromId(userId));
if (userData == null) {
return Task.FromResult((ApplicationUser)null);
}
var user = new ApplicationUser {
UserName = userData.UserName,
Id = userId,
ClientKey = userData.ClientId,
NewsFilter = userData.NewsFilter,
FullName = userData.FullName,
};
return Task.FromResult(user);
}
public Task<ApplicationUser> FindByNameAsync(string userName) {
throw new NotImplementedException();
}
}
// Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
public class ApplicationUserManager : UserManager<ApplicationUser> {
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store) {
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context) {
var manager =
new ApplicationUserManager(new StormpathUserStore(ObjectFactory.GetInstance<IStormpathService>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser>(manager) {
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator {
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 15;
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null) {
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity")) {TokenLifespan = TimeSpan.FromDays(14.0)};
}
return manager;
}
}
// Configure the application sign-in manager which is used in this application.
public class ApplicationSignInManager : SignInManager<ApplicationUser, string> {
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager) {
}
public override Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool isPersistent,
bool shouldLockout) {
return Task.FromResult(
new StormpathService(new Configuration()).AuthenticateUser(userName, password) != null
? SignInStatus.Success
: SignInStatus.Failure);
}
public override Task SignInAsync(ApplicationUser user, bool isPersistent, bool rememberBrowser) {
return base.SignInAsync(user, true, rememberBrowser);
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user) {
var result = user.GenerateUserIdentityAsync((ApplicationUserManager) UserManager).Result;
return Task.FromResult(result);
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options,
IOwinContext context) {
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
}
}

UserManager's async methods giving "Incorrect number of arguments for call to method Boolean Equals"

I've made custom User and UserStore classes.
Now I'm trying to register or login with a user, but I get the error
'Incorrect number of arguments supplied for call to method Boolean
Equals(System.String, System.String, System.StringComparison)'
The error is on line 411:
Line 409: }
Line 410: var user = new User() { UserName = model.Email, Email = model.Email };
--> Line 411: IdentityResult result = await UserManager.CreateAsync(user);
Line 412: if (result.Succeeded)
Line 413: {
I get this same error on
UserManager.FindAsync(user), UserManager.CreateAsync(user,password)
Now the error doesn't occur when I log in with an External Login, like Google, which also uses methods from UserManager. Entering the email works as well, but when the user has to be created from an External Login with the inserted email, it gives the CreateAsync error too.
EDIT The error also occurs on UserManager.Create(User user)
This is probably because the method in UserManager does an Equal on the user object's id, while it is expecting a string and mine is an int. But because I can't get a stacktrace inside the UserManager, and I have no idea how to override this method in the UserManager, I do not know how to solve this?
How can I fix this? Do I need to create my own UserManager? Or do I need another solution entirely?
My userstore code:
public class UserStore :
IUserStore<User>,
IUserPasswordStore<User>,
IUserSecurityStampStore<User>,
IUserEmailStore<User>,
IUserLoginStore<User>
{
private readonly NFCMSDbContext _db;
public UserStore(NFCMSDbContext db)
{
_db = db;
}
public UserStore()
{
_db = new NFCMSDbContext();
}
#region IUserStore
public Task CreateAsync(User user)
{
if (user == null)
throw new ArgumentNullException("user");
_db.Users.Add(user);
_db.Configuration.ValidateOnSaveEnabled = false;
return _db.SaveChangesAsync();
}
public Task DeleteAsync(User user)
{
if (user == null)
throw new ArgumentNullException("user");
_db.Users.Remove(user);
_db.Configuration.ValidateOnSaveEnabled = false;
return _db.SaveChangesAsync();
}
public Task<User> FindByIdAsync(string userId)
{
int userid;
if(int.TryParse(userId,out userid))
throw new ArgumentNullException("userId");
return _db.Users.Where(u => u.UserId == userid).FirstOrDefaultAsync();
}
(...)
User.cs:
public sealed class User : IUser<int>
{
public User()
{
UserLogins = new List<UserLogin>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public string PasswordHash { get; set; }
public string SecurityStamp { get; set; }
public string Email {get; set; }
public bool IsEmailConfirmed { get; set; }
int IUser<int>.Id
{
get { return UserId; }
}
public ICollection<UserLogin> UserLogins { get; private set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
Did you change your UserManager type to UserManager to tell it that your user key is now int instead of string?

Invalid cast when getting custom IPrincipal from HttpContext

After researching FormsAuthentication for a few days, I decided to store a serialized object in the FormsAuth cookie's UserData property and use a custom IPrincipal object for the HttpContext.Current.User.
Most of the guides I've found say to cast the IPrincipal object to your object. I get an invalid cast exception every time though. What am I doing wrong?
MyUserData
public class MyUserData
{
public long UserId { get; set; }
public string Username { get; set; }
public bool IsSuperUser { get; set; }
public string UnitCode { get; set; }
public string EmailAddress { get; set; }
public List<string> Roles { get; set; }
// Serialize
public override string ToString()
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
string result = serializer.Serialize(this);
return result;
}
// Deserialize
public static MyUserData FromString(string text)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.Deserialize<MyUserData>(text);
}
}
CustomPlatformPrincipal
public class MyCustomPrincipal : IPrincipal
{
public MyUserData MyUserData { get; set; }
public IIdentity Identity { get; private set; }
public MyCustomPrincipal(MyUserData myUserData)
{
MyUserData = myUserData;
Identity = new GenericIdentity(myUserData.Username);
}
public bool IsInRole(string role)
{
return MyUserData.Roles.Contains(role);
}
}
Global.asax.cs
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
{
return;
}
FormsAuthenticationTicket authTicket;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
return;
}
if (Context.User != null)
{
// the from string deserializes the data
MyUserData myUserData = MyUserData.FromString(authTicket.UserData);
Context.User = new MyCustomPrincipal(myUserData);
}
}
My Page
var myUserData = ((MyCustomPrincipal)(HttpContext.Current.User)).MyUserData;
// invalid cast exception (can't cast IPrincipal to MyCustomPrincipal)
Article I was following: http://primaryobjects.com/CMS/Article147.aspx
So it seems the only way I could get my data is to decrypt the auth cookie, then deserialize the authCookie's userData string.
Any suggestions?
Update
Tried following the suggestions on this SO question: Implementing a Custom Identity and IPrincipal in MVC
Code is below, but it didn't work.
[Serializable]
public class MyCustomPrincipal : IPrincipal, ISerializable
{
public CustomUserData CustomUserData { get; set; }
public IIdentity Identity { get; private set; }
//public MyCustomPrincipal (IIdentity identity) { Identity = identity; }
public MyCustomPrincipal(CustomUserData customUserData)
{
CustomUserData = customUserData;
Identity = new GenericIdentity(customUserData.Username);
}
public bool IsInRole(string role)
{
return PlatformUserData.Roles.Contains(role);
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (context.State == StreamingContextStates.CrossAppDomain)
{
MyCustomPrincipal principal = new MyCustomPrincipal (this.CustomUserData );
info.SetType(principal.GetType());
System.Reflection.MemberInfo[] serializableMembers;
object[] serializableValues;
serializableMembers = FormatterServices.GetSerializableMembers(principal.GetType());
serializableValues = FormatterServices.GetObjectData(principal, serializableMembers);
for (int i = 0; i < serializableMembers.Length; i++)
{
info.AddValue(serializableMembers[i].Name, serializableValues[i]);
}
}
else
{
throw new InvalidOperationException("Serialization not supported");
}
}
}
Did you run in the debug mode? You can put break point on HttpContext.Current.User, you will see what type the user was at that moment.
And from your Application_AuthenticateRequest method, there is no guarantee that the User will be your expected type. There are many exit points before reaching your custom type setup.
Even this code: Context.User != null. It was wrong with your expectation. I have not gone through the detail of the Context.User, however, in term of your context, you were expecting the Context.User was your custom user. So the valid check should be:
var custom = Context.Current as MyCustomPrinciple;
if(custom == null)
{
// Your construct code here.
}
My strongly suggestion is: you need to go in debug mode, to see exactly what was going on.

Categories