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