Index: src/main/java/hudson/security/LDAPSecurityRealm.java =================================================================== --- src/main/java/hudson/security/LDAPSecurityRealm.java (revision 10457) +++ src/main/java/hudson/security/LDAPSecurityRealm.java (working copy) @@ -42,6 +42,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; + /** * {@link SecurityRealm} implementation that uses LDAP for authentication. * @@ -78,6 +79,15 @@ * @see FilterBasedLdapUserSearch */ public final String userSearch; + + /** + * This defines the organizational unit that contains groups. + * + * Normally "ou=groups" + * + * @see FilterBasedLdapUserSearch + */ + public final String groupSearchBase; /* Other configurations that are needed: @@ -106,7 +116,7 @@ private final String managerPassword; @DataBoundConstructor - public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, String userSearch, String managerDN, String managerPassword) { + public LDAPSecurityRealm(String server, String rootDN, String userSearchBase, String userSearch, String groupSearchBase, String managerDN, String managerPassword) { this.server = server.trim(); if(Util.fixEmptyAndTrim(rootDN)==null) rootDN=Util.fixNull(inferRootDN(server)); this.rootDN = rootDN.trim(); @@ -112,6 +122,8 @@ this.rootDN = rootDN.trim(); this.userSearchBase = userSearchBase.trim(); if(Util.fixEmptyAndTrim(userSearch)==null) userSearch="uid={0}"; + if(Util.fixEmptyAndTrim(groupSearchBase)==null) groupSearchBase="ou=groups"; + this.groupSearchBase = groupSearchBase.trim(); this.userSearch = userSearch.trim(); this.managerDN = Util.fixEmpty(managerDN); if(Util.fixEmpty(managerPassword)==null) @@ -165,6 +177,7 @@ BeanBuilder builder = new BeanBuilder(); builder.parse(Hudson.getInstance().servletContext.getResourceAsStream("/WEB-INF/security/LDAPBindSecurityRealm.groovy"),binding); final WebApplicationContext appContext = builder.createApplicationContext(); + correctAuthoritiesPopulator(appContext); return new SecurityComponents( findBean(AuthenticationManager.class, appContext), @@ -182,6 +195,15 @@ } /** + * Adjust the authoritiesPopulator bean to have the correct groupSearchBase + * @param appContext + */ + private void correctAuthoritiesPopulator(WebApplicationContext appContext) { + DeferredCreationLdapAuthoritiesPopulator factory = (DeferredCreationLdapAuthoritiesPopulator) appContext.getBean("authoritiesPopulator"); + factory.setGroupSearchBase(groupSearchBase); + } + + /** * If the security realm is LDAP, try to pick up e-mail address from LDAP. */ public static final class MailAdressResolverImpl extends MailAddressResolver { Index: src/main/java/hudson/security/DeferredCreationLdapAuthoritiesPopulator.java =================================================================== --- src/main/java/hudson/security/DeferredCreationLdapAuthoritiesPopulator.java (revision 0) +++ src/main/java/hudson/security/DeferredCreationLdapAuthoritiesPopulator.java (revision 0) @@ -0,0 +1,135 @@ +/** + * + */ +package hudson.security; + +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.ldap.InitialDirContextFactory; +import org.acegisecurity.ldap.LdapDataAccessException; +import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator; +import org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; +import org.springframework.util.Assert; + +/** + * Implementation of {@link LdapAuthoritiesPopulator} that defers creation of a + * {@link DefaultLdapAuthoritiesPopulator} until one is needed. This is done to + * ensure that the groupSearchBase property can be set. + * + * @author justinedelson + * + */ +public class DeferredCreationLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator { + + /** + * A default role which will be assigned to all authenticated users if set. + */ + private String defaultRole = null; + + /** + * An initial context factory is only required if searching for groups is + * required. + */ + private InitialDirContextFactory initialDirContextFactory = null; + + /** + * Controls used to determine whether group searches should be performed + * over the full sub-tree from the base DN. + */ + private boolean searchSubtree = false; + + /** + * The ID of the attribute which contains the role name for a group + */ + private String groupRoleAttribute = "cn"; + + /** + * The base DN from which the search for group membership should be + * performed + */ + private String groupSearchBase = null; + + /** + * The pattern to be used for the user search. {0} is the user's DN + */ + private String groupSearchFilter = "(member={0})"; + + private String rolePrefix = "ROLE_"; + + private boolean convertToUpperCase = true; + + /** + * Constructor. + * + * @param initialDirContextFactory + * supplies the contexts used to search for user roles. + * @param groupSearchBase + * if this is an empty string the search will be performed from + * the root DN of the context factory. + */ + public DeferredCreationLdapAuthoritiesPopulator( + InitialDirContextFactory initialDirContextFactory, String groupSearchBase) { + this.setInitialDirContextFactory(initialDirContextFactory); + this.setGroupSearchBase(groupSearchBase); + } + + public GrantedAuthority[] getGrantedAuthorities(LdapUserDetails userDetails) + throws LdapDataAccessException { + return create().getGrantedAuthorities(userDetails); + } + + public void setConvertToUpperCase(boolean convertToUpperCase) { + this.convertToUpperCase = convertToUpperCase; + } + + public void setDefaultRole(String defaultRole) { + this.defaultRole = defaultRole; + } + + public void setGroupRoleAttribute(String groupRoleAttribute) { + this.groupRoleAttribute = groupRoleAttribute; + } + + public void setGroupSearchBase(String groupSearchBase) { + this.groupSearchBase = groupSearchBase; + } + + public void setGroupSearchFilter(String groupSearchFilter) { + this.groupSearchFilter = groupSearchFilter; + } + + public void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) { + this.initialDirContextFactory = initialDirContextFactory; + } + + public void setRolePrefix(String rolePrefix) { + this.rolePrefix = rolePrefix; + } + + public void setSearchSubtree(boolean searchSubtree) { + this.searchSubtree = searchSubtree; + } + + /** + * Create a new DefaultLdapAuthoritiesPopulator object. + * + * @return a DefaultLdapAuthoritiesPopulator. + */ + private DefaultLdapAuthoritiesPopulator create() { + Assert + .hasText(groupSearchBase, + "groupSearchBase must be non-null by the time DefaultLdapAuthoritiesPopulator is instantiated."); + DefaultLdapAuthoritiesPopulator populator = new DefaultLdapAuthoritiesPopulator( + initialDirContextFactory, groupSearchBase); + populator.setConvertToUpperCase(convertToUpperCase); + if (defaultRole != null) { + populator.setDefaultRole(defaultRole); + } + populator.setGroupRoleAttribute(groupRoleAttribute); + populator.setGroupSearchFilter(groupSearchFilter); + populator.setRolePrefix(rolePrefix); + populator.setSearchSubtree(searchSubtree); + return populator; + } + +} Index: src/main/resources/hudson/security/LDAPSecurityRealm/config.jelly =================================================================== --- src/main/resources/hudson/security/LDAPSecurityRealm/config.jelly (revision 10457) +++ src/main/resources/hudson/security/LDAPSecurityRealm/config.jelly (working copy) @@ -13,6 +13,9 @@ + + +