From 72a91027abbc544f11020ec8e20d8717451da4d7 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 14 Jun 2013 16:10:20 +0100 Subject: [PATCH] [FIXES JENKINS-17281] Adding configuration options for the filters used to search for groups. - It is somewhat confusing that there are two `group search filters` so I have decided to rename one. - The new name for the `groupSearchFilter` that is controlled from `LDAPBindSecurityRealm.groovy` is the `groupMembershipFilter` as this filter is used to determine what groups a specific user is a member of - That leaves `groupSearchFilter` as a nice clean name for the filter to search for named groups. - This should still respect any existing configuration, i.e. leaving these fields blank will leave the existing defaults or existing overrides in place... but it will make life easier for users going forward - Took quite some digging to figure out exactly what these filters were for... hopefully I have left things in a more obvious framing for anyone else following - I would like a better way to apply the `groupMembershipFilter` override, but this was the cleanest way I could maintain backwards compatibility --- .../hudson/security/LDAPSecurityRealm.java | 53 ++++++++++++++++++- .../security/LDAPSecurityRealm/config.jelly | 8 ++- .../help-cache.html | 0 .../webapp/help-groupMembershipFilter.html | 30 +++++++++++ src/main/webapp/help-groupSearchFilter.html | 24 +++++++++ 5 files changed, 112 insertions(+), 3 deletions(-) rename src/main/{resources/hudson/security/LDAPSecurityRealm => webapp}/help-cache.html (100%) create mode 100644 src/main/webapp/help-groupMembershipFilter.html create mode 100644 src/main/webapp/help-groupSearchFilter.html diff --git a/src/main/java/hudson/security/LDAPSecurityRealm.java b/src/main/java/hudson/security/LDAPSecurityRealm.java index 47bd820..f493c8f 100644 --- a/src/main/java/hudson/security/LDAPSecurityRealm.java +++ b/src/main/java/hudson/security/LDAPSecurityRealm.java @@ -262,6 +262,23 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm { */ public final String groupSearchBase; + /** + * Query to locate an entry that identifies the group, given the group name string. If non-null it will override + * the default specified by {@link #GROUP_SEARCH} + * + * @since 1.5 + */ + public final String groupSearchFilter; + + /** + * Query to locate the group entries that a user belongs to, given the user object. {0} + * is the user's full DN while {1} is the username. If non-null it will override the default specified in + * {@code LDAPBindSecurityRealm.groovy} + * + * @since 1.5 + */ + public final String groupMembershipFilter; + /* Other configurations that are needed: @@ -314,19 +331,35 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm { */ private transient Map>> groupDetailsCache = null; + /** + * @deprecated retained for backwards binary compatibility. + */ + @Deprecated public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, String userSearch, String groupSearchBase, String managerDN, String managerPassword, boolean inhibitInferRootDN) { this(server, rootDN, userSearchBase, userSearch, groupSearchBase, managerDN, managerPassword, inhibitInferRootDN, false); } + /** + * @deprecated retained for backwards binary compatibility. + */ + @Deprecated public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, String userSearch, String groupSearchBase, String managerDN, String managerPassword, boolean inhibitInferRootDN, boolean disableMailAddressResolver) { this(server, rootDN, userSearchBase, userSearch, groupSearchBase, managerDN, managerPassword, inhibitInferRootDN, disableMailAddressResolver, null); } - @DataBoundConstructor + /** + * @deprecated retained for backwards binary compatibility. + */ + @Deprecated public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, String userSearch, String groupSearchBase, String managerDN, String managerPassword, boolean inhibitInferRootDN, boolean disableMailAddressResolver, CacheConfiguration cache) { + this(server, rootDN, userSearchBase, userSearch, groupSearchBase, null, null, managerDN, managerPassword, inhibitInferRootDN, disableMailAddressResolver, cache); + } + + @DataBoundConstructor + public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, String userSearch, String groupSearchBase, String groupSearchFilter, String groupMembershipFilter, String managerDN, String managerPassword, boolean inhibitInferRootDN, boolean disableMailAddressResolver, CacheConfiguration cache) { this.server = server.trim(); this.managerDN = fixEmpty(managerDN); this.managerPassword = Scrambler.scramble(fixEmpty(managerPassword)); @@ -337,6 +370,8 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm { userSearch = fixEmptyAndTrim(userSearch); this.userSearch = userSearch!=null ? userSearch : "uid={0}"; this.groupSearchBase = fixEmptyAndTrim(groupSearchBase); + this.groupSearchFilter = fixEmptyAndTrim(groupSearchFilter); + this.groupMembershipFilter = fixEmptyAndTrim(groupMembershipFilter); this.disableMailAddressResolver = disableMailAddressResolver; this.cache = cache; } @@ -357,6 +392,14 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm { return cache == null ? null : cache.getTtl(); } + public String getGroupMembershipFilter() { + return groupMembershipFilter; + } + + public String getGroupSearchFilter() { + return groupSearchFilter; + } + /** * Infer the root DN. * @@ -416,6 +459,11 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm { ldapTemplate = new LdapTemplate(findBean(InitialDirContextFactory.class, appContext)); + if (groupMembershipFilter != null) { + AuthoritiesPopulatorImpl authoritiesPopulator = findBean(AuthoritiesPopulatorImpl.class, appContext); + authoritiesPopulator.setGroupSearchFilter(groupMembershipFilter); + } + return new SecurityComponents( findBean(AuthenticationManager.class, appContext), new LDAPUserDetailsService(appContext)); @@ -457,10 +505,11 @@ public class LDAPSecurityRealm extends AbstractPasswordBasedSecurityRealm { // TODO: obtain a DN instead so that we can obtain multiple attributes later String searchBase = groupSearchBase != null ? groupSearchBase : ""; + String searchFilter = groupSearchFilter != null ? groupSearchFilter : GROUP_SEARCH; final Set groups = cachedGroups != null ? cachedGroups : (Set) ldapTemplate - .searchForSingleAttributeValues(searchBase, GROUP_SEARCH, new String[]{groupname}, "cn"); + .searchForSingleAttributeValues(searchBase, searchFilter, new String[]{groupname}, "cn"); if (cache != null && cachedGroups == null && !groups.isEmpty()) { synchronized (this) { if (groupDetailsCache == null) { diff --git a/src/main/resources/hudson/security/LDAPSecurityRealm/config.jelly b/src/main/resources/hudson/security/LDAPSecurityRealm/config.jelly index 9dce3a4..e69dd2e 100644 --- a/src/main/resources/hudson/security/LDAPSecurityRealm/config.jelly +++ b/src/main/resources/hudson/security/LDAPSecurityRealm/config.jelly @@ -43,6 +43,12 @@ THE SOFTWARE. + + + + + + - + diff --git a/src/main/resources/hudson/security/LDAPSecurityRealm/help-cache.html b/src/main/webapp/help-cache.html similarity index 100% rename from src/main/resources/hudson/security/LDAPSecurityRealm/help-cache.html rename to src/main/webapp/help-cache.html diff --git a/src/main/webapp/help-groupMembershipFilter.html b/src/main/webapp/help-groupMembershipFilter.html new file mode 100644 index 0000000..2155993 --- /dev/null +++ b/src/main/webapp/help-groupMembershipFilter.html @@ -0,0 +1,30 @@ +
+

+ When Jenkins resolves a user, the next step in the resolution process is to determine the LDAP groups that + the user belongs to. This field controls the search filter that is used to determine group membership. + If left blank, the default filter will be used. +

+

+ The default default filter is: +

+
(| (member={0}) (uniqueMember={0}) (memberUid={1}))
+

+ This can be overridden by creating a file $JENKINS_HOME/LDAPBindSecurityRealm.groovy. Irrespective + of what the default is, setting this filter to a non-blank value will determine the filter used. +

+

+ You are normally safe leaving this field unchanged, however for large LDAP servers where you are seeing messages + such as OperationNotSupportedException - Function Not Implemented, + Administrative Limit Exceeded or similar periodically when trying to login, then that would + indicate that you should change to a more optimum filter for your LDAP server, namely one that queries only + the required field, such as: +

+
(member={0})
+

+ Note: in this field there are two available substitutions: +

+
    +
  • {0} - the fully qualified DN of the user
  • +
  • {1} - the username portion of the user
  • +
+
\ No newline at end of file diff --git a/src/main/webapp/help-groupSearchFilter.html b/src/main/webapp/help-groupSearchFilter.html new file mode 100644 index 0000000..25760bc --- /dev/null +++ b/src/main/webapp/help-groupSearchFilter.html @@ -0,0 +1,24 @@ +
+

+ When Jenkins is asked to determine if a named group exists, it uses a default filter of: +

+
(& (cn={0}) (| (objectclass=groupOfNames) (objectclass=groupOfUniqueNames) (objectclass=posixGroup)))
+

+ relative to the Group search base to determine if there is a group with the specified name ( + {0} is substituted by the name being searched for) +

+

+ If you know your LDAP server only stores group information in one specific object class, then you can improve + group search performance by restricting the filter to just the required objectclass. +

+

+ Note: if you are using the LDAP security realm to connect to Active Directory (as opposed to using the + Active Directory plugin's + security realm) then you will need to change this filter to: +

+
(& (cn={0}) (objectclass=group) )
+

+ Note: if you leave this empty, the default search filter will be used, unless the + hudson.security.LDAPSecurityRealm.groupSearch has been set to modify the default. +

+
\ No newline at end of file