diff --git a/core/src/main/java/hudson/security/LDAPSecurityRealm.java b/core/src/main/java/hudson/security/LDAPSecurityRealm.java index 8588e6a..beee7f3 100644 --- a/core/src/main/java/hudson/security/LDAPSecurityRealm.java +++ b/core/src/main/java/hudson/security/LDAPSecurityRealm.java @@ -340,33 +340,32 @@ public class LDAPSecurityRealm extends SecurityRealm { return new SecurityComponents( findBean(AuthenticationManager.class, appContext), - new UserDetailsService() { - final LdapUserSearch ldapSearch = findBean(LdapUserSearch.class, appContext); - final LdapAuthoritiesPopulator authoritiesPopulator = findBean(LdapAuthoritiesPopulator.class, appContext); - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { - try { - LdapUserDetails ldapUser = ldapSearch.searchForUser(username); - // LdapUserSearch does not populate granted authorities (group search). - // Add those, as done in LdapAuthenticationProvider.createUserDetails(). - if (ldapUser != null) { - LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(ldapUser); - GrantedAuthority[] extraAuthorities = authoritiesPopulator.getGrantedAuthorities(ldapUser); - for (int i = 0; i < extraAuthorities.length; i++) { - user.addAuthority(extraAuthorities[i]); - } - ldapUser = user.createUserDetails(); - } - return ldapUser; - } catch (LdapDataAccessException e) { - LOGGER.log(Level.WARNING, "Failed to search LDAP for username="+username,e); - throw new UserMayOrMayNotExistException(e.getMessage(),e); - } - } - }); + new LDAPUserDetailsService(appContext)); } + /** + * Lookup a group; given input must match the configured syntax for group names + * in WEB-INF/security/LDAPBindSecurityRealm.groovy's authoritiesPopulator entry. + * The defaults are a prefix of "ROLE_" and using all uppercase. This method will + * not return any data if the given name lacks the proper prefix and/or case. + */ @Override public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException { + // Check proper syntax based on acegi configuration + String prefix = ""; + boolean onlyUpperCase = false; + try { + AuthoritiesPopulatorImpl api = (AuthoritiesPopulatorImpl) + ((LDAPUserDetailsService)getSecurityComponents().userDetails).authoritiesPopulator; + prefix = api.rolePrefix; + onlyUpperCase = api.convertToUpperCase; + } catch (Exception ignore) { } + if (onlyUpperCase && !groupname.equals(groupname.toUpperCase())) + throw new UsernameNotFoundException(groupname + " should be all uppercase"); + if (!groupname.startsWith(prefix)) + throw new UsernameNotFoundException(groupname + " is missing prefix: " + prefix); + groupname = groupname.substring(prefix.length()); + // TODO: obtain a DN instead so that we can obtain multiple attributes later String searchBase = groupSearchBase != null ? groupSearchBase : ""; final Set groups = (Set)ldapTemplate.searchForSingleAttributeValues(searchBase, GROUP_SEARCH, @@ -382,6 +381,34 @@ public class LDAPSecurityRealm extends SecurityRealm { }; } + public static class LDAPUserDetailsService implements UserDetailsService { + public final LdapUserSearch ldapSearch; + public final LdapAuthoritiesPopulator authoritiesPopulator; + LDAPUserDetailsService(WebApplicationContext appContext) { + ldapSearch = findBean(LdapUserSearch.class, appContext); + authoritiesPopulator = findBean(LdapAuthoritiesPopulator.class, appContext); + } + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { + try { + LdapUserDetails ldapUser = ldapSearch.searchForUser(username); + // LdapUserSearch does not populate granted authorities (group search). + // Add those, as done in LdapAuthenticationProvider.createUserDetails(). + if (ldapUser != null) { + LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(ldapUser); + GrantedAuthority[] extraAuthorities = authoritiesPopulator.getGrantedAuthorities(ldapUser); + for (int i = 0; i < extraAuthorities.length; i++) { + user.addAuthority(extraAuthorities[i]); + } + ldapUser = user.createUserDetails(); + } + return ldapUser; + } catch (LdapDataAccessException e) { + LOGGER.log(Level.WARNING, "Failed to search LDAP for username="+username,e); + throw new UserMayOrMayNotExistException(e.getMessage(),e); + } + } + } + /** * If the security realm is LDAP, try to pick up e-mail address from LDAP. */ @@ -414,14 +441,32 @@ public class LDAPSecurityRealm extends SecurityRealm { * {@link LdapAuthoritiesPopulator} that adds the automatic 'authenticated' role. */ public static final class AuthoritiesPopulatorImpl extends DefaultLdapAuthoritiesPopulator { + // Make these available (private in parent class and no get methods!) + String rolePrefix; + boolean convertToUpperCase; public AuthoritiesPopulatorImpl(InitialDirContextFactory initialDirContextFactory, String groupSearchBase) { super(initialDirContextFactory, groupSearchBase); + // These match the defaults in acegi 1.0.5; set again to store in non-private fields: + setRolePrefix("ROLE_"); + setConvertToUpperCase(true); } @Override protected Set getAdditionalRoles(LdapUserDetails ldapUser) { return Collections.singleton(AUTHENTICATED_AUTHORITY); } + + @Override + public void setRolePrefix(String rolePrefix) { + super.setRolePrefix(rolePrefix); + this.rolePrefix = rolePrefix; + } + + @Override + public void setConvertToUpperCase(boolean convertToUpperCase) { + super.setConvertToUpperCase(convertToUpperCase); + this.convertToUpperCase = convertToUpperCase; + } } @Extension diff --git a/war/resources/WEB-INF/security/LDAPBindSecurityRealm.groovy b/war/resources/WEB-INF/security/LDAPBindSecurityRealm.groovy index 2f77800..589270d 100644 --- a/war/resources/WEB-INF/security/LDAPBindSecurityRealm.groovy +++ b/war/resources/WEB-INF/security/LDAPBindSecurityRealm.groovy @@ -65,6 +65,8 @@ authoritiesPopulator(AuthoritiesPopulatorImpl, initialDirContextFactory, Util.fi // see DefaultLdapAuthoritiesPopulator for other possible configurations searchSubtree = true; groupSearchFilter = "(| (member={0}) (uniqueMember={0}) (memberUid={1}))"; + // rolePrefix = "ROLE_"; // Default is "ROLE_" + // convertToUpperCase = false; // Default is true } authenticationManager(ProviderManager) {