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

[JENKINS-34545] - Support case insensitive user IDs in roles #43

Closed
wants to merge 28 commits into from

Conversation

seanmceligot
Copy link

This should fix JENKINS-34545 and related issues. It allows case insensitive matching to user/group values for Global and Item roles in Assign Roles.

Copy link
Member

@oleg-nenashev oleg-nenashev left a comment

Choose a reason for hiding this comment

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

And it would be a breaking change... I tried to do something similar at some point, bu it appeared that there are Jenkins users who really rely on the current behavior.

I would be fine if the plugin becomes case-insensitive by default, but there should be an option to restore the original behavior

@seanmceligot
Copy link
Author

OK, I'll look into that.

@seanmceligot
Copy link
Author

Here's a screenshot of what I have now. I was thinking Assign Roles screen is the most relevant place to put this but let me know what you think. Current it doesn't reload map without restarting Jenkins. I'm looking into that.

roles

bldr.append(name);
bldr.append(':');
bldr.append(pattern);
return bldr.toString();
Copy link
Member

Choose a reason for hiding this comment

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

with String.format() you can do it in one line

@oleg-nenashev
Copy link
Member

Maybe it makes sense to move the setting to the common Authorization Strategy configuration screen (config.jelly in https://github.com/jenkinsci/role-strategy-plugin/tree/master/src/main/resources/com/michelin/cio/hudson/plugins/rolestrategy/RoleBasedAuthorizationStrategy) and making the setter a @DataBoundSetter. Otherwise this option will immediately become unmanageable from Configuration as Code.

No strong opinion tho, it is a wider problem than this PR

@oleg-nenashev oleg-nenashev changed the title case insensitive user ID's in roles [JENKINS-34545] - case insensitive user ID's in roles Sep 16, 2018
Copy link
Member

@oleg-nenashev oleg-nenashev left a comment

Choose a reason for hiding this comment

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

After some consideration, I believe that using standard data binding is required here. JCasC 1.0 has been released recently, and I do not want to introduce compatibility issues

@@ -78,8 +78,10 @@
.expireAfterWrite(Settings.USER_DETAILS_CACHE_EXPIRATION_TIME_SEC, TimeUnit.SECONDS)
.build();

private final transient boolean caseInsensitiveUser;
Copy link
Member

Choose a reason for hiding this comment

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

I am not sure what is the intention here. With the current logic the flag will be always false after the restart, and adding new roles from API won't behave as expected

Copy link
Author

Choose a reason for hiding this comment

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

The real configuration variable is in RoleBasedAuthorizationStrategy. That one is currently marshed/unmarshed explicitly in that RoleBasedAuthorizationStrategy.ConverterImpl. I just followed what was already there. Does the ConvererImpl entirely need to be converted or do you want the new variable managed elsewhere?

This RoleMap.caseInsensitiveUser is a copy needed to create the TreeSet when RoleMap.addRole is called. I made it transient because I didn't want it stored in config.xml and final to show that it was not the config.xml variable. It loads correctly at startup from config.xml, but I need to recreate all the TreeSets when it's changed realtime.

config.xml

class="com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy">
    <caseInsensitiveUser>true</caseInsensitiveUser>

new variable placement on Configure Global Security page.

caseinsensitveconfiguresecurity

@daniel-beck
Copy link
Member

Why not integrate with case sensitivity of the chosen security realm? Or what am I missing?

reload grantedRoles when caseinsensitive changes
[maven-release-plugin] copy for tag role-strategy-2.9.0
@ingwarsw
Copy link

@oleg-nenashev Any plans to integrate this..
Its really hard to explain users to login using always the same case..

@oleg-nenashev
Copy link
Member

oleg-nenashev commented Nov 10, 2018 via email

@seanmceligot
Copy link
Author

Just an update. My original fix was two lines lines of code.

master...seanmceligot:case-insensitive-2.9.0

I started to add an configuration option turn it on and off as requested for legacy users, but my change wasn't compatible with configuration as code. I'd need to delve into the CASC framework if I wanted to continue.

@kiran1abc
Copy link

@seanmceligot can we expect any progress on this issue in future.

@seanmceligot
Copy link
Author

@seanmceligot can we expect any progress on this issue in future.

Now should be a good time since configuration as code was merged. Let me look at adding the configuration parameter again and make sure it's CASC compatible.

@kiran1abc
Copy link

@seanmceligot thanks that helps us a lot.

@seanmceligot
Copy link
Author

screenshot

@oleg-nenashev oleg-nenashev self-requested a review July 16, 2019 00:39
@oleg-nenashev
Copy link
Member

Thanks @seanmceligot ! It would be really great to get it over the line

@oleg-nenashev oleg-nenashev changed the title [JENKINS-34545] - case insensitive user ID's in roles [JENKINS-34545] - Support case insensitive user IDs in roles Jul 16, 2019
Copy link
Contributor

@res0nance res0nance left a comment

Choose a reason for hiding this comment

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

Not much to add other than there are several code formatting issues and what seems to be excessive logging. hasPermission is called quite often and to prevent polluting logs it might make sense to log it at a finer level.

Copy link
Member

@AbhyudayaSharma AbhyudayaSharma left a comment

Choose a reason for hiding this comment

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

There seems to be quite a big performance penalty of this change apart from formatting issues.

@@ -141,7 +158,7 @@ private boolean hasPermission(String sid, Permission permission, RoleType roleTy
new RoleWalker() {
public void perform(Role current) {
if (current.hasAnyPermission(permissions)) {
if (grantedRoles.get(current).contains(sid)) {
if (hasSid( grantedRoles.get(current), userIdStrategy, sid)) {
Copy link
Member

Choose a reason for hiding this comment

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

contains() in a set is a relatively fast operation. Now, you have to traverse the entire set to see if the role actually contains the sid.

The benchmarks suggest the same:
image
The results were compared to the last successful build of master (4efef4f).

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I can't use Set because it's optimized at creation time time but IDStrategy as a configuration can be changed anytime. I suppose I could use a case insensitive Set and then verify it using the correct IDStrategy at lookup time. Or, rebuild the Set every time the configuration changes, but that's a but trickier to get right.

Copy link
Member

Choose a reason for hiding this comment

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

Rebuilding set would be a good approach IMO.
I would be fine if the entire RoleStrategy object gets rebuilt on a configuration change

@@ -127,12 +141,15 @@ public RoleMap(@Nonnull SortedMap<Role,Set<String>> grantedRoles) {
public static void invalidateDangerousPermissions() {
PermissionHelper.DANGEROUS_PERMISSIONS.forEach(implyingPermissionCache::remove);
}

private boolean hasSid(Set<String> roleAssignements, IdStrategy userIdStrategy, String sid) {
Copy link
Member

Choose a reason for hiding this comment

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

Typo here: roleAssignementsroleAssignments

Copy link
Author

Choose a reason for hiding this comment

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

fixed. Thank you. seanmceligot@3be3e3b

@@ -1,6 +1,7 @@
jenkins:
authorizationStrategy:
roleBased:
userIdStrategyChoice: DEFAULT
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we have a test case where the userIdStrategyChoice is not present?

Copy link
Member

Choose a reason for hiding this comment

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

We should. Also, it is odd that all tests require "DEFAULT" and none really tests values set

Copy link
Member

@oleg-nenashev oleg-nenashev left a comment

Choose a reason for hiding this comment

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

Thanks @seanmceligot ! It is getting closer. but ultimately we need a performance degradation issue solved. Also, would be nice to get new autotests, Javadoc and formatting fixes

agentRoles.invalidateCache();
itemRoles.invalidateCache();
}
public void setUserIdStrategyChoiceStr(String userIdStrategyChoice) {
Copy link
Member

Choose a reason for hiding this comment

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

I suggest not making such public method.

Suggested change
public void setUserIdStrategyChoiceStr(String userIdStrategyChoice) {
@Restricted(NoExternalUse.class)
public void setUserIdStrategyChoiceStr(String userIdStrategyChoice) {

}
public void setUserIdStrategyChoiceStr(String userIdStrategyChoice) {
LOGGER.info("setUserIdStrategyChoice"+ userIdStrategyChoice);
setUserIdStrategyChoice( UserIdStrategyChoice.valueOf(userIdStrategyChoice) );
Copy link
Member

Choose a reason for hiding this comment

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

What if a non-existent value is passed. It will fail here, right?

@@ -680,6 +757,7 @@ public void doRolesSubmit(StaplerRequest req, StaplerResponse rsp) throws Unsupp

req.setCharacterEncoding("UTF-8");
JSONObject json = req.getSubmittedForm();
LOGGER.info("doRolesSubmit: "+json.toString());
Copy link
Member

Choose a reason for hiding this comment

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

Unrelated. If you need it, please use FINEST level or lower

@@ -696,6 +774,7 @@ public void doAssignSubmit(StaplerRequest req, StaplerResponse rsp) throws Unsup

req.setCharacterEncoding("UTF-8");
JSONObject json = req.getSubmittedForm();
LOGGER.info("doAssignSubmit: "+json.toString());
Copy link
Member

Choose a reason for hiding this comment

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

same

@@ -141,7 +158,7 @@ private boolean hasPermission(String sid, Permission permission, RoleType roleTy
new RoleWalker() {
public void perform(Role current) {
if (current.hasAnyPermission(permissions)) {
if (grantedRoles.get(current).contains(sid)) {
if (hasSid( grantedRoles.get(current), userIdStrategy, sid)) {
Copy link
Member

Choose a reason for hiding this comment

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

Rebuilding set would be a good approach IMO.
I would be fine if the entire RoleStrategy object gets rebuilt on a configuration change

@@ -33,6 +36,8 @@
@Restricted({NoExternalUse.class})
public class RoleBasedAuthorizationStrategyConfigurator extends BaseConfigurator<RoleBasedAuthorizationStrategy> {

private static final Logger LOGGER = Logger.getLogger(RoleBasedAuthorizationStrategyConfigurator.class.getName());
Copy link
Member

Choose a reason for hiding this comment

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

tabs vs. spaces

Suggested change
private static final Logger LOGGER = Logger.getLogger(RoleBasedAuthorizationStrategyConfigurator.class.getName());
private static final Logger LOGGER = Logger.getLogger(RoleBasedAuthorizationStrategyConfigurator.class.getName());

@@ -7,8 +7,7 @@
import hudson.model.FreeStyleProject;
import hudson.model.Item;
import hudson.model.User;
import hudson.security.AuthorizationStrategy;
import io.jenkins.plugins.casc.ConfigurationContext;
import hudson.security.AuthorizationStrategy; import io.jenkins.plugins.casc.ConfigurationContext;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
import hudson.security.AuthorizationStrategy; import io.jenkins.plugins.casc.ConfigurationContext;
import hudson.security.AuthorizationStrategy;
import io.jenkins.plugins.casc.ConfigurationContext;

@@ -56,7 +56,7 @@
<td class="left-most">${title}</td>
<j:forEach var="r" items="${it.strategy.getGrantedRoles(attrs.type)}">
<td width="*">
<f:checkbox name="[${r.key.name}]" checked="${r.value.contains(attrs.sid)}"/>
<f:checkbox name="[${r.key.name}]" checked="${r.value.contains(attrs.sid)}"/>
Copy link
Member

Choose a reason for hiding this comment

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

Revert it

Suggested change
<f:checkbox name="[${r.key.name}]" checked="${r.value.contains(attrs.sid)}"/>
<f:checkbox name="[${r.key.name}]" checked="${r.value.contains(attrs.sid)}"/>

@@ -1,6 +1,7 @@
jenkins:
authorizationStrategy:
roleBased:
userIdStrategyChoice: DEFAULT
Copy link
Member

Choose a reason for hiding this comment

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

We should. Also, it is odd that all tests require "DEFAULT" and none really tests values set

@@ -127,12 +141,15 @@ public RoleMap(@Nonnull SortedMap<Role,Set<String>> grantedRoles) {
public static void invalidateDangerousPermissions() {
PermissionHelper.DANGEROUS_PERMISSIONS.forEach(implyingPermissionCache::remove);
}

private boolean hasSid(Set<String> roleAssignments, IdStrategy userIdStrategy, String sid) {
return roleAssignments.stream().anyMatch( rolesid -> userIdStrategy.compare(rolesid, sid) == 0 );
Copy link
Member

Choose a reason for hiding this comment

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

I was going through how Matrix Authorization plugin handles it. Looks to me that this check is incomplete. Since these sids can be group names too and there exists SecurityRealm#getGroupIdStrategy(), you will need to take care of that.

https://github.com/jenkinsci/matrix-auth-plugin/blob/8c7b7bcec67a03ab73498486fc2029b2b886541d/src/main/java/org/jenkinsci/plugins/matrixauth/AuthorizationContainer.java#L152

Copy link
Author

Choose a reason for hiding this comment

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

I will look that at. Good catch.

@zhaojinggang
Copy link

This should fix JENKINS-34545 and related issues. It allows case insensitive matching to user/group values for Global and Item roles in Assign Roles.

Just check if there's any plan to release this feature?

@seanmceligot seanmceligot deleted the caseinsensitive branch June 25, 2020 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants