ASP Identity Role Does Not Exist Error - c#

I am trying to create a page in my MVC .NET CORE application in which a user can change the roles of other users. At this point the view and model bindings all work great, but when actually trying to save the roles back to the database I am getting an error saying that the roles don't exists. My approach is this:
Get list of roles from the model.
Delete existing roles for specified user.
Add roles back using list from model.
Do this all in a transaction in case of error.
My code in the controller is as follows:
var user = await _userManager.FindByIdAsync(id);
if (user == null)
{
return View("Error");
}
using (_context.Database.BeginTransaction())
{
var removeResult = await _userManager.RemoveFromRolesAsync(user, await _userManager.GetRolesAsync(user));
if (!removeResult.Succeeded)
{
return View("Error");
}
var addResult = await _userManager.AddToRolesAsync(user, model.MemberRoles);
if (!addResult.Succeeded)
{
return View("Error");
}
}
Where model.MemberRoles as a List<string> of roles. The strange part is is that this process is failing on _userManager.RemoveFromRolesAsync, even though I am passing the existing roles of the user directly into the function. I have tried ditching the UserManager class and going with ef but had no luck. I also verified that the roles in the model match what is in the database. Is there anything obviously wrong that would cause this to fail? Thanks.
Edit
Here is the error I am running into:
InvalidOperationException: Role ADMINISTRATOR does not exist.
Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`4.<AddToRoleAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
And here are the roles defined in my database:

When you try to add a user to a role, it first tries to find the role. In Sql Profiler, you can see that it is actually searching by the NormalizedName field rather than the Name field.
exec sp_executesql N'
SELECT TOP(2) [r].[Id], [r].[ConcurrencyStamp], [r].[Name], [r].[NormalizedName]
FROM [AspNetRoles] AS [r]
WHERE [r].[NormalizedName] = #__normalizedRoleName_0',N'#__normalizedRoleName_0 nvarchar(256)',#__normalizedRoleName_0=N'ADMINISTRATOR'
The solution is to set the NormalizedName field to the Uppercase of the Name field when you create the Roles:
So in the SeedData class:
/* Create Roles */
IEnumerable<UserRole> allRoles = Enum.GetValues(typeof(UserRole)).Cast<UserRole>();
foreach (UserRole role in allRoles)
{
if (!await roleManager.RoleExistsAsync(role.ToString()))
{
await roleManager.CreateAsync(new IdentityRole {
Name = role.ToString(),
NormalizedName = role.ToString().ToUpper()
});
}
}

I'm actually surprised you're not getting an InvalidCastException instead, as it's not possible to directly cast string[] to List<string>. Not really sure why you're doing it that way in the first place, since you could just as easily just call .ToList(), instead of .ToArray(), and call it a day, if you want a list. Or, since the return type of GetRolesAsync is IList<string>, anyways, you don't even really need to do that. Right now, that's the only problem I see with your code, though.

Related

Previously no-tracking entities get tracked upon calling Savechages(). Is this normal?

I have a simple controller in which I create and add a user and a profile to the database
public async ActionResult AddUserAndProfile(string id)
{
_context.Users.Add(new User { Id = id });
_context.SaveChanges(); // If this line is removed, error doesn't occur.
var profile = new Profile
{
Id = "id",
User = _context.Users.AsNoTracking().FirstOrDefault(u => u.Id.Equals(id))
}; // Exception given on this line.
_context.Profiles.Add(profile);
_context.SaveChanges();
return Ok();
}
When I call this controller with id = "0" I get the following exception:
The instance of entity type 'User' cannot be tracked because another instance with the key value '{Id: 0}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
However, if I remove the first SaveChanges() call, this error is not shown.
1st question: Is it intended that entities get tracked after saving changes, wouldn't it make sense that they get tracked beforehand only? Also afaik, add actions don't mark entities as tracked.
2nd question: When is the best time to call SaveChages() in this situation? (It is important to note that add user and add profile actions are in different repo methods in the real project. I simplified the code here.)
3rd question: What is the best way to add foreign keys in situations like this?
You should be able to save whole hierarchy within one call to SaveChanges:
var profile = new Profile
{
Id = "id",
User = new User { Id = id }
};
_context.Profiles.Add(profile);
_context.SaveChanges();
It should answer 2nd and 3rd questions I think. As for the first one - there is a github issue with request to "Do not track after SaveChanges()".
Since you have different methods you can just set the UserId property on Profile (if you have explicitly added it to Profile entity) directly.
Save the user you create, and then specify that user for the profile.
var user = new User { Id = id };
_context.Users.Add(user);
_context.SaveChanges();
var profile = new Profile {Id = "id", User = user};

Asp.Net Identity - Custom Database Access UserManager

Is is possible to extend the UserStore/UserManager to have access to a custom table within an override method (without creating another dbcontext)?
I'm working on a legacy database which we integrated with Identity. Recently we added a new table, Clients, to the database that has a one to one relationship with the Users table. What would like to do is override the FindAsync method in the UserManager class to do call the base method, grab the user, and then based on the user's client Id stored in the table, fetch the client name and insert that into the user model.
The following is what I have attempted; however, to me I think this is wrong approach and would like some suggestions on how to correctly apply what I'm trying to do.
public override async Task<ApplicationUser> FindAsync(string userName, string password)
{
ApplicationUser user = await base.FindAsync(userName, password);
if (user != null && user.ClientID != null)
{
using (var ctx = new ApplicationDbContext(HostPage.ConnectionString))
{
user.Client = ctx.Clients.Where(p => p.ClientID == user.ClientID).FirstOrDefault();
}
}
return user;
}
You could modify SQL in base.FindAsync() to perform a join to your table thus executing a single query.

Kentico UserInfoProvider.IsAuthorizedPerClass not working as expected

I'm attempting to check if a given user has access to a specific Custom Table.
Based on the example listed on the kentico documentation to check permissions for a custom table, I have setup a similar call, using my custom table class name and userinfo, but the call to "UserInfoProvider.IsAuthorizedPerClass" always return false:
private bool CheckCustomTableReadPermission(UserInfo user = null)
{
// Gets the user object
//UserInfo user = UserInfoProvider.GetUserInfo("CMSEditor");
//UserInfo user = UserInfoProvider.GetUserInfo("someothervalidusername");
//UserInfo user = CurrentUser;
//normally outside of this function
UserInfo CurrentUser = MembershipContext.AuthenticatedUser;
string CustomTableClassName = "Namespc.TblName";
if (user == null)
{
user = CurrentUser;
}
if (user != null)
{
// Checks whether the user has the Read permission for the CMS.MenuItem page type
if (UserInfoProvider.IsAuthorizedPerClass(CustomTableClassName, "Read", SiteContext.CurrentSiteName, user))
{
// Perform an action according to the result
return true;
}
}
return false;
}
Can anyone also mention what the valid permission name strings are, other than "Read"? (e.g.: "Modify"? "Delete"? "Insert"?)
Does UserInfoProvider.IsAuthorizedPerClass resolve all memberships of the given user, or does it only check if the user is explicitly added to the Custom Table?
Any suggestions? We're using Kentico v8.2.25
Thanks!
Victor
What about doing it the same way as it's done in
CMS\CMSModules\CustomTables\Tools\CustomTable_Data_EditItem.aspx.cs
which is:
DataClassInfo dci = DataClassInfoProvider.GetDataClassInfo(customTableId);
dci.CheckPermissions(PermissionsEnum.Read, SiteContext.CurrentSiteName, MembershipContext.AuthenticatedUser)
And the possible permissions are located in CMS.DataEngine.PermissionsEnum. (Read, Modify, Create, Delete, Destroy)
EDIT:
I'm, dumb. You're assigning a default value to the user param, not an auto-assigned value. I would still check to make sure you're getting the user info you're expecting, because that seems to be the most likely cause for the problem.
You seem to be running into a problem here:
private bool CheckCustomTableReadPermission(UserInfo user = null)
Since you're auto-assigning your user parameter to null when your method is called, the following statement will always be true:
if (user == null)
{
user = CurrentUser;
}
And you will never reach your other statement:
if (user != null)
{
// Checks whether the user has the Read permission for the CMS.MenuItem page type
if (UserInfoProvider.IsAuthorizedPerClass(CustomTableClassName, "Read", SiteContext.CurrentSiteName, user))
{
// Perform an action according to the result
return true;
}
}
So your method will always return false.
The IsAuthorizedPerClass() function checks only the user's permissions for the class that you provide to check against and only the specific permission you provide for it to check (e.g. "Read"). So yes, it's only going to see if the user has the Read permission for your custom table.
I'm not 100% certain what all the permissions are, although it appears to be stored in an enum. I'll get back to you on that one in a bit. Hope this helps :)
The IsAuthorizedPerClass() method will return true only if the user's role has been granted permission explicitly within the role's Permissions for that class. All other times, it will return false even if the user is in fact able to Read/Modify/etc. the custom table.
To get the correct permission strings, you can use CMS.DataEngine.PermissionsEnum.<type>.ToString()
To check whether a user has permissions to Read a specific custom table, you will need to make the following 3 checks in order:
UserInfoProvider.IsAuthorizedPerUIElement("CMS.CustomTables","CustomTables",SiteContext.CurrentSiteName,user)
UserInfoProvider.IsAuthorizedPerResource("CMS.CustomTables", PermissionsEnum.Read.ToString(), SiteContext.CurrentSiteName, user)
UserInfoProvider.IsAuthorizedPerClass(CustomTableClassName, PermissionsEnum.Read.ToString(), SiteContext.CurrentSiteName, user)

Add method - MVC - working with data entry into two tables

I have two classes:
**User
UseP where** User have its id (PK) and UserP has the id (FK) of User.
I'm working on MVC 4. Using Fluent API.
The classes are mapped within the project to connect to the database. My project is split into controllers, services, models and views.
I have a User screen that uses a UserPr field.
How can I instantiate it in class without being the way down? (that way does not work because of the relationship, otherwise work):
**CLASS USER.CS**
public string Login { get { return this.UserP.login } set { login = value }}
inclusion screen to add the User of items, I should also change the status of the screen, this status is within UserP.
Controller
public ViewResultBase Add(User model)
{
if (this.ModelState.IsValid)
{
try
{
this.Service.SaveUserP(model);
return this.SuccessView(true);
}
catch (ValidationException exception)
{
base.AddValidationErrors(exception);
return base.PartialView(model);
}
}
else
{
return base.PartialView(model);
}
}
Service
public void SaveUserP(User item)
{
//save fields from User (Running ok)
base.context.Usuario.Add(item);
//Attempt to save the login and their status in UserP
foreach (var userP in base.context.UserP.Where(x => x.IdUser == item.Id))
{
item.login = userP.Login;
userP.ParticipantType = 3;
base.context.UsersP.Add(userP);
}
}
I've tried that way, but I could not.
If the item.login is only working because login stand as [notmapped] in User.cs
To summarize: In the inclusion screen (User) I have a field that should bring (UserP) login.
When I trigger the method of inclusion, he should save the fields and save the User log in using UsuarioP IdUsuario as key and also changing the status of login (ParticipantType = 3)
The error I get:
Invalid column login (because really there is no login in User)
Time to debug it includes only the User fields, and even through the foreach.
I do not know how to operate, can help me? What if I have not been clear, I put more details
You will need to save the User input first before your foreach loop.
base.context.SaveChanges();

Update custom user profile fields with SimpleMembershipProvider?

I added a custom field to the UserProfile table named ClassOfYear and I'm able to get the data into the profile during registration like this:
var confirmationToken = WebSecurity.CreateUserAndAccount(model.UserName,
model.Password,
propertyValues: new { ClassOfYear = model.ClassOfYear },
requireConfirmationToken: true);
However, now I want to be able to update the profile when I manage it but I can't seem to find a method to do so. Do I need to simply update the UserProfile table myself? If not, what is the appropriate way of doing this?
FYI, I'm using Dapper as my data access layer, just in case it matters. But, like stated, I can just update the UserProfile table via Dapper if that's what I'm supposed to do, I just figured that the WebSecurity class, or something similar, had a way already since the custom user profile fields are integrated with the CreateUserAndAccount method.
Thanks all!
There is nothing in the SimpleMembershipProvider code that does anything with additional fields except upon create.
Simply query the values yourself from your ORM.
You can use the WebSecurity.GetUserId(User.Identity.Name) to get the user's id and then Dapper to query the UserProfile table.
Just in case anyone facing the same problem. After fighting a lot with the SimpleMembership I got a solution that populates both the webpages_Membership and my custom Users table. For clarification follow my code:
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
TUsuario userDTO= new TUSer()
{
Name = model.Name,
Login = model.Login,
Pass = model.Pass.ToString(CultureInfo.InvariantCulture),
Active = true,
IdCompany = model.IdCompany,
IdUserGroup = model.IdUserGroup,
};
try
{
WebSecurity.CreateUserAndAccount(model.Login, model.Pass, new { IdUser = new UserDAL().Seq.NextVal(), Name = userDTO.Name, Login = userDTO.Login, Active = userDTO.Active, Pass = userDTO.Pass, IdCompany = userDTO.IdCompany, IdUserGroup = userDTO.IdUserGroup });
WebSecurity.Login(model.Login, model.Pass);
After cursing the framework a lot, that gave me a bliss of fresh air :)
PS.: The users table is specified in the global.asax file using the WebSecurity.InitializeDatabaseConnection functon.

Categories

Resources