Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consistent comparing of role names #15076

Closed
wants to merge 7 commits into from

Conversation

gvkries
Copy link
Contributor

@gvkries gvkries commented Jan 12, 2024

Always compare role names case insensitive. Also refactored all role names into a static helper.

Fixes #15057

@gvkries
Copy link
Contributor Author

gvkries commented Jan 12, 2024

Sorry for the large PR out of the blue, I thought it might be useful 😇

@@ -102,7 +103,7 @@ public async Task<IdentityResult> DeleteAsync(IRole role, CancellationToken canc
handler.RoleRemovedAsync(roleToRemove.RoleName), roleToRemove, _logger);

var roles = await LoadRolesAsync();
roleToRemove = roles.Roles.FirstOrDefault(r => r.RoleName == roleToRemove.RoleName);
roleToRemove = roles.Roles.FirstOrDefault(r => string.Equals(r.RoleName, roleToRemove.RoleName, StringComparison.OrdinalIgnoreCase));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the behavior.

@@ -115,7 +116,7 @@ public async Task<IRole> FindByIdAsync(string roleId, CancellationToken cancella
// While updating find a role from the loaded document being mutated.
var roles = _updating ? await LoadRolesAsync() : await GetRolesAsync();

var role = roles.Roles.FirstOrDefault(x => x.RoleName == roleId);
var role = roles.Roles.FirstOrDefault(x => string.Equals(x.RoleName, roleId, StringComparison.OrdinalIgnoreCase));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this changes the behavior. Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it alters behavior, but enhances the reliability of the RoleStore. The comparison of roles by name was handled inconsistently across the entire source code. If anyone used multiple roles that differed only in casing, they are already facing issues.

The specific problem I encountered was that default permissions were not applied to roles in UpdateAsync due to the case sensitivity. Merely making UpdateAsync case-insensitive wouldn't have been a logical solution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should be done is storing the role name in the database as normalized and always use == to compare normalize values. I don't think we should use invariant check all over the place

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normalized value is already stored in NormalizedRoleName which is used by MS Identity. But I don't think we gain anything by using that. It would always require to either normalize all parameters or also using case insensitive comparing. The normalized value is also managed by MS Identity and not by OC on its own.
As it is currently implemented, RoleName is used as the ID for roles and it is a user-provided value. That makes it kind of fragile and I think case insensitive comparing is the least intrusive solution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gvkries
I don't remember all the details, I would have to look at all the places we use role name to provide more feedback. Which is why I suggested to replace string with constants in this PR.

But from what I remember is that we do not store normalized roles in the Users properties. I think we need to change how to store the role name and add migration to update existing stored names. Then, anytime we do comparisons, we would normalize the name and do a normalized name comparison.

public const string Editor = "Editor";
public const string Moderator = "Moderator";

public static IEnumerable<string> SystemRoles => s_systemRoleNames;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these helper should stay where they were and keep role name in here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed that because the RoleHelper class had very few references internally and I don't expect many uses outside of the internal projects. Also I think it makes sense to bundle system roles and the role names together and I thought RoleNames would be easier to discover, additionally because I changed the namespace to match the one of IRole too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RoleNames should be constant variable whereas RoleHelper is a class for helper function. If someone is using the RoleNames, they'll have a breaking change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't understand what you mean. Do you want to move RoleNames inside RoleHelper? Breaking is the removal of RoleHelper, but I don't think that matters. It contains one static field and has 4 internal references. I don't expect any external reference.

I messed up the namespace of RoleNames anyway. It was meant to be in OrchardCore.Security side-by-side with IRole and Role.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gvkries
I suggest to keep the existing RoleHelper in addition to the new RoleNames.

I also, think you should to only replace role name with the new constants in this PR. Then using a follow up PR maybe suggest the casing changes. This way the logic of changing the casing will be much easier to follow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to take this, it was just a suggestion to make things easier in future. I'm going to close this PR and provide the fix only.

@hishamco
Copy link
Member

@gvkries please don't ask all to review the PR, I know it might happen acciedently when you push a PR :)

@MikeAlhayek
Copy link
Member

MikeAlhayek commented Jan 12, 2024

@hishamco this is an automated process (specially they are code owners) and it not done by @gvkries

@hishamco
Copy link
Member

As expected :)

@gvkries gvkries closed this Jan 17, 2024
@gvkries gvkries deleted the gvkries/role-names-15057 branch April 23, 2024 11:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Role name casing
3 participants