diff --git a/core/src/main/java/org/acegisecurity/userdetails/UserDetailsManager.java b/core/src/main/java/org/acegisecurity/userdetails/UserDetailsManager.java new file mode 100644 index 0000000000..9729f0fc0d --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/UserDetailsManager.java @@ -0,0 +1,42 @@ +package org.acegisecurity.userdetails; + +/** + * An extension of the {@link UserDetailsService} which provides the ability + * to create new users and update existing ones. + * + * @author Luke Taylor + * @since 2.0 + * @version $Id$ + */ +public interface UserDetailsManager extends UserDetailsService { + + /** + * Create a new user with the supplied details. + */ + void createUser(UserDetails user); + + /** + * Update the specified user. + */ + void updateUser(UserDetails user); + + /** + * Remove the user with the given login name from the system. + */ + void deleteUser(String username); + + /** + * Modify the current user's password. + * + * + * @param oldPassword current password (for re-authentication if required) + * @param newPassword the password to change to + */ + void changePassword(String oldPassword, String newPassword); + + /** + * Check if a user with the supplied login name exists in the system. + */ + boolean userExists(String username); + +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/ldap/InetOrgPerson.java b/core/src/main/java/org/acegisecurity/userdetails/ldap/InetOrgPerson.java new file mode 100644 index 0000000000..e6c06bc477 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/ldap/InetOrgPerson.java @@ -0,0 +1,106 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acegisecurity.userdetails.ldap; + +import org.springframework.ldap.support.DirContextOperations; +import org.springframework.ldap.support.DirContextAdapter; + +/** + * UserDetails implementation whose properties are based on a subset of the + * LDAP schema for inetOrgPerson. + * + *

+ * The username will be mapped from the uid attribute by default. + *

+ * + * @author Luke + * @version $Id$ + */ +public class InetOrgPerson extends Person { + private String mail; + private String uid; + private String employeeNumber; + private String destinationIndicator; + + public String getMail() { + return mail; + } + + public String getUid() { + return uid; + } + + public String getEmployeeNumber() { + return employeeNumber; + } + + public String getDestinationIndicator() { + return destinationIndicator; + } + + protected void populateContext(DirContextAdapter adapter) { + super.populateContext(adapter); + adapter.setAttributeValue("mail", mail); + adapter.setAttributeValue("uid", uid); + adapter.setAttributeValue("employeeNumber", employeeNumber); + adapter.setAttributeValue("destinationIndicator", destinationIndicator); + adapter.setAttributeValues("objectclass", new String[] {"top", "person", "organizationalPerson", "inetOrgPerson"}); + } + + public static class Essence extends Person.Essence { + public Essence() { + } + + public Essence(InetOrgPerson copyMe) { + super(copyMe); + setMail(copyMe.getMail()); + setUid(copyMe.getUid()); + setDestinationIndicator(copyMe.getDestinationIndicator()); + setEmployeeNumber(copyMe.getEmployeeNumber()); + } + + public Essence(DirContextOperations ctx) { + super(ctx); + setMail(ctx.getStringAttribute("mail")); + setUid(ctx.getStringAttribute("uid")); + setEmployeeNumber(ctx.getStringAttribute("employeeNumber")); + setDestinationIndicator(ctx.getStringAttribute("destinationIndicator")); + } + + protected LdapUserDetailsImpl createTarget() { + return new InetOrgPerson(); + } + + public void setMail(String email) { + ((InetOrgPerson) instance).mail = email; + } + + public void setUid(String uid) { + ((InetOrgPerson) instance).uid = uid; + + if(instance.getUsername() == null) { + setUsername(uid); + } + } + + public void setEmployeeNumber(String no) { + ((InetOrgPerson) instance).employeeNumber = no; + } + + public void setDestinationIndicator(String destination) { + ((InetOrgPerson) instance).destinationIndicator = destination; + } + } +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/ldap/InetOrgPersonContextMapper.java b/core/src/main/java/org/acegisecurity/userdetails/ldap/InetOrgPersonContextMapper.java new file mode 100644 index 0000000000..474b4081bb --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/ldap/InetOrgPersonContextMapper.java @@ -0,0 +1,46 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acegisecurity.userdetails.ldap; + +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.GrantedAuthority; +import org.springframework.ldap.support.DirContextOperations; +import org.springframework.ldap.support.DirContextAdapter; +import org.springframework.util.Assert; + + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class InetOrgPersonContextMapper implements UserDetailsContextMapper { + + public UserDetails mapUserFromContext(DirContextOperations ctx, String username, GrantedAuthority[] authorities) { + InetOrgPerson.Essence p = new InetOrgPerson.Essence(ctx); + + p.setUsername(username); + p.setAuthorities(authorities); + + return p.createUserDetails(); + + } + + public void mapUserToContext(UserDetails user, DirContextAdapter ctx) { + Assert.isInstanceOf(InetOrgPerson.class, user, "UserDetails must be an InetOrgPerson instance"); + + InetOrgPerson p = (InetOrgPerson) user; + p.populateContext(ctx); + } +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetails.java b/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetails.java index 8d128b6938..f42f69b9a0 100644 --- a/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetails.java +++ b/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetails.java @@ -34,6 +34,7 @@ public interface LdapUserDetails extends UserDetails { * The attributes for the user's entry in the directory (or a subset of them, depending on what was * retrieved from the directory) * + * @deprecated Map additional attributes to properties in a subclass rather than accessing them here. * @return the user's attributes, or an empty array if none were obtained, never null. */ Attributes getAttributes(); diff --git a/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsImpl.java b/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsImpl.java index 1c0e6d5c4d..ca372ac790 100644 --- a/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsImpl.java +++ b/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsImpl.java @@ -18,10 +18,13 @@ package org.acegisecurity.userdetails.ldap; import org.acegisecurity.GrantedAuthority; import org.springframework.util.Assert; +import org.springframework.ldap.support.DirContextOperations; +import org.springframework.ldap.support.DirContextAdapter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Iterator; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; @@ -30,10 +33,14 @@ import javax.naming.ldap.Control; /** * A UserDetails implementation which is used internally by the Ldap services. It also contains the user's - * distinguished name and a set of attributes that have been retrieved from the Ldap server.

An instance may be - * created as the result of a search, or when user information is retrieved during authentication.

- *

An instance of this class will be used by the LdapAuthenticationProvider to construct the final - * user details object that it returns.

+ * distinguished name and a set of attributes that have been retrieved from the Ldap server. + *

+ * An instance may be created as the result of a search, or when user information is retrieved during authentication. + *

+ *

+ * An instance of this class will be used by the LdapAuthenticationProvider to construct the final user details + * object that it returns. + *

* * @author Luke Taylor * @version $Id$ @@ -41,7 +48,6 @@ import javax.naming.ldap.Control; public class LdapUserDetailsImpl implements LdapUserDetails { //~ Static fields/initializers ===================================================================================== - private static final long serialVersionUID = 1L; private static final GrantedAuthority[] NO_AUTHORITIES = new GrantedAuthority[0]; private static final Control[] NO_CONTROLS = new Control[0]; @@ -110,10 +116,14 @@ public class LdapUserDetailsImpl implements LdapUserDetails { * Variation of essence pattern. Used to create mutable intermediate object */ public static class Essence { - private LdapUserDetailsImpl instance = createTarget(); + protected LdapUserDetailsImpl instance = createTarget(); private List mutableAuthorities = new ArrayList(); - public Essence() {} + public Essence() { } + + public Essence(DirContextOperations ctx) { + setDn(ctx.getDn().toString()); + } public Essence(LdapUserDetails copyMe) { setDn(copyMe.getDn()); @@ -128,14 +138,27 @@ public class LdapUserDetailsImpl implements LdapUserDetails { setAuthorities(copyMe.getAuthorities()); } - LdapUserDetailsImpl createTarget() { + protected LdapUserDetailsImpl createTarget() { return new LdapUserDetailsImpl(); } - public Essence addAuthority(GrantedAuthority a) { - mutableAuthorities.add(a); + /** Adds the authority to the list, unless it is already there, in which case it is ignored */ + public void addAuthority(GrantedAuthority a) { + if(!hasAuthority(a)) { + mutableAuthorities.add(a); + } + } - return this; + private boolean hasAuthority(GrantedAuthority a) { + Iterator authorities = mutableAuthorities.iterator(); + + while(authorities.hasNext()) { + GrantedAuthority authority = (GrantedAuthority) authorities.next(); + if(authority.equals(a)) { + return true; + } + } + return false; } public LdapUserDetails createUserDetails() { @@ -155,62 +178,44 @@ public class LdapUserDetailsImpl implements LdapUserDetails { return (GrantedAuthority[]) mutableAuthorities.toArray(new GrantedAuthority[0]); } - public Essence setAccountNonExpired(boolean accountNonExpired) { + public void setAccountNonExpired(boolean accountNonExpired) { instance.accountNonExpired = accountNonExpired; - - return this; } - public Essence setAccountNonLocked(boolean accountNonLocked) { + public void setAccountNonLocked(boolean accountNonLocked) { instance.accountNonLocked = accountNonLocked; - - return this; } - public Essence setAttributes(Attributes attributes) { + public void setAttributes(Attributes attributes) { instance.attributes = attributes; - - return this; } - public Essence setAuthorities(GrantedAuthority[] authorities) { + public void setAuthorities(GrantedAuthority[] authorities) { mutableAuthorities = new ArrayList(Arrays.asList(authorities)); - - return this; } public void setControls(Control[] controls) { instance.controls = controls; } - public Essence setCredentialsNonExpired(boolean credentialsNonExpired) { + public void setCredentialsNonExpired(boolean credentialsNonExpired) { instance.credentialsNonExpired = credentialsNonExpired; - - return this; } - public Essence setDn(String dn) { + public void setDn(String dn) { instance.dn = dn; - - return this; } - public Essence setEnabled(boolean enabled) { + public void setEnabled(boolean enabled) { instance.enabled = enabled; - - return this; } - public Essence setPassword(String password) { + public void setPassword(String password) { instance.password = password; - - return this; } - public Essence setUsername(String username) { + public void setUsername(String username) { instance.username = username; - - return this; } } } diff --git a/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsManager.java b/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsManager.java new file mode 100644 index 0000000000..9d4216a7a8 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsManager.java @@ -0,0 +1,414 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acegisecurity.userdetails.ldap; + +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.UsernameNotFoundException; +import org.acegisecurity.userdetails.UserDetailsManager; +import org.acegisecurity.ldap.LdapUtils; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.GrantedAuthorityImpl; +import org.acegisecurity.Authentication; +import org.acegisecurity.context.SecurityContextHolder; +import org.springframework.dao.DataAccessException; +import org.springframework.util.Assert; +import org.springframework.ldap.support.DistinguishedName; +import org.springframework.ldap.support.DirContextAdapter; +import org.springframework.ldap.LdapTemplate; +import org.springframework.ldap.AttributesMapper; +import org.springframework.ldap.ContextSource; +import org.springframework.ldap.ContextExecutor; +import org.springframework.ldap.SearchExecutor; +import org.springframework.ldap.EntryNotFoundException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.naming.*; +import javax.naming.ldap.LdapContext; +import javax.naming.directory.*; +import java.util.*; + +/** + * An Ldap implementation of UserDetailsManager. + *

+ * It is designed around a standard setup where users and groups/roles are stored under separate contexts, + * defined by the "userDnBase" and "groupSearchBase" properties respectively. + *

+ *

+ * In this case, LDAP is being used purely to retrieve information and this class can be used in place of any other + * UserDetailsService for authentication. Authentication isn't performed directly against the directory, unlike with the + * LDAP authentication provider setup. + *

+ * + * + * @author Luke Taylor + * @since 2.0 + */ +public class LdapUserDetailsManager implements UserDetailsManager { + private final Log logger = LogFactory.getLog(LdapUserDetailsManager.class); + + /** The DN under which users entries are stored */ + private DistinguishedName userDnBase = new DistinguishedName("cn=users"); + /** The DN under which groups are stored */ + private DistinguishedName groupSearchBase = new DistinguishedName("cn=groups"); + + /** The attribute which contains the user login name, and which is used by default to build the DN for new users */ + private String usernameAttributeName = "uid"; + /** Password attribute name */ + private String passwordAttributeName = "userPassword"; + + /** The attribute which corresponds to the role name of a group. */ + private String groupRoleAttributeName ="cn"; + /** The attribute which contains members of a group */ + private String groupMemberAttributeName = "uniquemember"; + + private String rolePrefix = "ROLE_"; + + /** The pattern to be used for the user search. {0} is the user's DN */ + private String groupSearchFilter = "(uniquemember={0})"; + /** + * The strategy used to create a UserDetails object from the LDAP context, username and list of authorities. + * This should be set to match the required UserDetails implementation. + */ + private UserDetailsContextMapper userDetailsMapper = new InetOrgPersonContextMapper(); + + private LdapTemplate template; + + /** Default context mapper used to create a set of roles from a list of attributes */ + private AttributesMapper roleMapper = new AttributesMapper() { + + public Object mapFromAttributes(Attributes attributes) throws NamingException { + Attribute roleAttr = attributes.get(groupRoleAttributeName); + + NamingEnumeration ne = roleAttr.getAll(); + // assert ne.hasMore(); + Object group = ne.next(); + String role = group.toString(); + + return new GrantedAuthorityImpl(rolePrefix + role.toUpperCase()); + } + }; + + private String[] attributesToRetrieve = null; + + public LdapUserDetailsManager(ContextSource contextSource) { + template = new LdapTemplate(contextSource); + } + + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { + DistinguishedName dn = buildDn(username); + GrantedAuthority[] authorities = getUserAuthorities(dn, username); + + logger.debug("Loading user '"+ username + "' with DN '" + dn + "'"); + + DirContextAdapter userCtx = loadUserAsContext(dn, username); + + return userDetailsMapper.mapUserFromContext(userCtx, username, authorities); + } + + private UserContext loadUserAsContext(final DistinguishedName dn, final String username) { + return (UserContext) template.executeReadOnly(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws NamingException { + try { + Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve); + return new UserContext(attrs, LdapUtils.getFullDn(dn, ctx)); + } catch(NameNotFoundException notFound) { + throw new UsernameNotFoundException("User " + username + " not found", notFound); + } + } + }); + } + + /** + * Changes the password for the current user. The username is obtained from the security context. + *

+ * If the old password is supplied, the update will be made by rebinding as the user, thus modifying the password + * using the user's permissions. If oldPassword is null, the update will be attempted using a + * standard read/write context supplied by the context source. + *

+ * + * @param oldPassword the old password + * @param newPassword the new value of the password. + */ + public void changePassword(final String oldPassword, final String newPassword) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(authentication, + "No authentication object found in security context. Can't change current user's password!"); + + String username = authentication.getName(); + + + + + logger.debug("Changing password for user '"+ username); + + final DistinguishedName dn = buildDn(username); + final ModificationItem[] passwordChange = new ModificationItem[] { + new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(passwordAttributeName, newPassword)) + }; + + if(oldPassword == null) { + template.modifyAttributes(dn, passwordChange); + return; + } + + template.executeReadWrite(new ContextExecutor() { + + public Object executeWithContext(DirContext dirCtx) throws NamingException { + LdapContext ctx = (LdapContext) dirCtx; + ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool"); + ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, LdapUtils.getFullDn(dn, ctx).toUrl()); + ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, oldPassword); + ctx.reconnect(null); + + ctx.modifyAttributes(dn, passwordChange); + + return null; + } + }); + } + + /** + * + * @param dn the distinguished name of the entry - may be either relative to the base context + * or a complete DN including the name of the context (either is supported). + * @param username the user whose roles are required. + * @return the granted authorities returned by the group search + */ + GrantedAuthority[] getUserAuthorities(final DistinguishedName dn, final String username) { + SearchExecutor se = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) throws NamingException { + DistinguishedName fullDn = LdapUtils.getFullDn(dn, ctx); + SearchControls ctrls = new SearchControls(); + ctrls.setReturningAttributes(new String[] {groupRoleAttributeName}); + + return ctx.search(groupSearchBase, groupSearchFilter, new String[] {fullDn.toUrl(), username}, ctrls); + } + }; + + LdapTemplate.AttributesMapperCallbackHandler roleCollector = + template.new AttributesMapperCallbackHandler(roleMapper); + + template.search(se, roleCollector); + List authorities = roleCollector.getList(); + + return (GrantedAuthority[]) authorities.toArray(new GrantedAuthority[authorities.size()]); + } + +// protected String getRoleFilter(DistinguishedName dn, String username) { +// return new EqualsFilter("uniquemember", dn.toString()).encode(); +// } + + public void createUser(UserDetails user) { + DirContextAdapter ctx = new DirContextAdapter(); + copyToContext(user, ctx); + DistinguishedName dn = buildDn(user.getUsername()); + // Check for any existing authorities which might be set for this DN + GrantedAuthority[] authorities = getUserAuthorities(dn, user.getUsername()); + + if(authorities.length > 0) { + removeAuthorities(dn, authorities); + } + + logger.debug("Creating new user '"+ user.getUsername() + "' with DN '" + dn + "'"); + + template.bind(dn, ctx, null); + + addAuthorities(dn, user.getAuthorities()); + } + + public void updateUser(UserDetails user) { +// Assert.notNull(attributesToRetrieve, "Configuration must specify a list of attributes in order to use update."); + DistinguishedName dn = buildDn(user.getUsername()); + + logger.debug("Updating user '"+ user.getUsername() + "' with DN '" + dn + "'"); + + GrantedAuthority[] authorities = getUserAuthorities(dn, user.getUsername()); + + UserContext ctx = loadUserAsContext(dn, user.getUsername()); + ctx.setUpdateMode(true); + copyToContext(user, ctx); + + // Remove the objectclass attribute from the list of mods (if present). + List mods = new LinkedList(Arrays.asList(ctx.getModificationItems())); + + ListIterator modIt = mods.listIterator(); + while(modIt.hasNext()) { + ModificationItem mod = (ModificationItem) modIt.next(); + Attribute a = mod.getAttribute(); + if("objectclass".equalsIgnoreCase(a.getID())) { + modIt.remove(); + } + } + + template.modifyAttributes(dn, (ModificationItem[]) mods.toArray(new ModificationItem[mods.size()])); + +// template.rebind(dn, ctx, null); + // Remove the old authorities and replace them with the new one + removeAuthorities(dn, authorities); + addAuthorities(dn, user.getAuthorities()); + } + + public void deleteUser(String username) { + DistinguishedName dn = buildDn(username); + removeAuthorities(dn, getUserAuthorities(dn, username)); + template.unbind(dn); + } + + public boolean userExists(String username) { + DistinguishedName dn = buildDn(username); + + try { + Object obj = template.lookup(dn); + if (obj instanceof Context) { + LdapUtils.closeContext((Context) obj); + } + return true; + } catch(EntryNotFoundException e) { + return false; + } + } + + /** + * Constructs a DN from a username. + *

+ * The default implementation appends a name component to the userDnBase context using the + * usernameAttributeName property. So if the uid attribute is used to store the username, and the + * base DN is cn=users and we are creating a new user called "sam", then the DN will be + * uid=sam,cn=users. + * + * @param username the user name used for authentication. + * @return the corresponding DN, relative to the base context. + */ + protected DistinguishedName buildDn(String username) { + DistinguishedName dn = new DistinguishedName(userDnBase); + + dn.add(usernameAttributeName, username); + + return dn; + } + + /** + * Creates a DN from a group name. + * + * @param group the name of the group + * @return the DN of the corresponding group, including the groupSearchBase + */ + protected DistinguishedName buildGroupDn(String group) { + DistinguishedName dn = new DistinguishedName(groupSearchBase); + dn.add(groupRoleAttributeName, group.toLowerCase()); + + return dn; + } + + protected void copyToContext(UserDetails user, DirContextAdapter ctx) { + userDetailsMapper.mapUserToContext(user, ctx); + } + + private void addAuthorities(DistinguishedName userDn, GrantedAuthority[] authorities) { + modifyAuthorities(userDn, authorities, DirContext.ADD_ATTRIBUTE); + } + + private void removeAuthorities(DistinguishedName userDn, GrantedAuthority[] authorities) { + modifyAuthorities(userDn, authorities, DirContext.REMOVE_ATTRIBUTE); + } + + private void modifyAuthorities(final DistinguishedName userDn, final GrantedAuthority[] authorities, final int modType) { + template.executeReadWrite(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws NamingException { + for(int i=0; i < authorities.length; i++) { + GrantedAuthority authority = authorities[i]; + String group = convertAuthorityToGroup(authority); + DistinguishedName fullDn = LdapUtils.getFullDn(userDn, ctx); + ModificationItem addGroup = new ModificationItem(modType, + new BasicAttribute(groupMemberAttributeName, fullDn.toUrl())); + + ctx.modifyAttributes(buildGroupDn(group), new ModificationItem[] {addGroup}); + } + return null; + } + }); + } + + private String convertAuthorityToGroup(GrantedAuthority authority) { + String group = authority.getAuthority(); + + if(group.startsWith(rolePrefix)) { + group = group.substring(rolePrefix.length()); + } + + return group; + } + + public void setUsernameAttributeName(String usernameAttributeName) { + this.usernameAttributeName = usernameAttributeName; + } + + public void setPasswordAttributeName(String passwordAttributeName) { + this.passwordAttributeName = passwordAttributeName; + } + + public void setGroupSearchBase(String groupSearchBase) { + this.groupSearchBase = new DistinguishedName(groupSearchBase); + } + + public void setGroupRoleAttributeName(String groupRoleAttributeName) { + this.groupRoleAttributeName = groupRoleAttributeName; + } + + public void setUserDnBase(String userDnBase) { + this.userDnBase = new DistinguishedName(userDnBase); + } + + public void setAttributesToRetrieve(String[] attributesToRetrieve) { + Assert.notNull(attributesToRetrieve); + this.attributesToRetrieve = attributesToRetrieve; + } + + public void setUserDetailsMapper(UserDetailsContextMapper userDetailsMapper) { + this.userDetailsMapper = userDetailsMapper; + } + + /** + * Sets the name of the multi-valued attribute which holds the DNs of users who are members of a group. + *

+ * Usually this will be uniquemember (the default value) or member. + *

+ * + * @param groupMemberAttributeName the name of the attribute used to store group members. + */ + public void setGroupMemberAttributeName(String groupMemberAttributeName) { + Assert.hasText(groupMemberAttributeName); + this.groupMemberAttributeName = groupMemberAttributeName; + this.groupSearchFilter = "(" + groupMemberAttributeName + "={0})"; + } + + public void setRoleMapper(AttributesMapper roleMapper) { + this.roleMapper = roleMapper; + } + + /** + * This class allows us to set the updateMode property of DirContextAdapter when updating existing users. + */ + private static class UserContext extends DirContextAdapter { + public UserContext(Attributes pAttrs, Name dn) { + super(pAttrs, dn); + } + + protected void setUpdateMode(boolean mode) { + super.setUpdateMode(mode); + } + } +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsMapper.java b/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsMapper.java index 05952f3ffb..4cf9f6bba9 100644 --- a/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsMapper.java +++ b/core/src/main/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsMapper.java @@ -24,6 +24,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; +import org.springframework.ldap.ContextMapper; +import org.springframework.ldap.UncategorizedLdapException; +import org.springframework.ldap.AttributesIntegrityViolationException; +import org.springframework.ldap.support.DirContextAdapter; import javax.naming.NamingEnumeration; import javax.naming.NamingException; @@ -37,10 +41,11 @@ import javax.naming.directory.Attributes; * @author Luke Taylor * @version $Id$ */ -public class LdapUserDetailsMapper implements LdapEntryMapper { +public class LdapUserDetailsMapper implements ContextMapper { //~ Instance fields ================================================================================================ private final Log logger = LogFactory.getLog(LdapUserDetailsMapper.class); +// private String usernameAttributeName = "uid"; private String passwordAttributeName = "userPassword"; private String rolePrefix = "ROLE_"; private String[] roleAttributes = null; @@ -48,42 +53,45 @@ public class LdapUserDetailsMapper implements LdapEntryMapper { //~ Methods ======================================================================================================== - public Object mapAttributes(String dn, Attributes attributes) - throws NamingException { + public Object mapFromContext(Object ctxObj) { + Assert.isInstanceOf(DirContextAdapter.class, ctxObj, "Can only map from DirContextAdapter instances"); + + DirContextAdapter ctx = (DirContextAdapter)ctxObj; + String dn = ctx.getNameInNamespace(); + + logger.debug("Mapping user details from context with DN: " + dn); + LdapUserDetailsImpl.Essence essence = new LdapUserDetailsImpl.Essence(); - essence.setDn(dn); - essence.setAttributes(attributes); + essence.setAttributes(ctx.getAttributes()); - Attribute passwordAttribute = attributes.get(passwordAttributeName); + Attribute passwordAttribute = ctx.getAttributes().get(passwordAttributeName); if (passwordAttribute != null) { essence.setPassword(mapPassword(passwordAttribute)); } +// essence.setUsername(mapUsername(ctx)); + // Map the roles for (int i = 0; (roleAttributes != null) && (i < roleAttributes.length); i++) { - Attribute roleAttribute = attributes.get(roleAttributes[i]); + String[] rolesForAttribute = ctx.getStringAttributes(roleAttributes[i]); - if (roleAttribute == null) { + if (rolesForAttribute == null) { logger.debug("Couldn't read role attribute '" + roleAttributes[i] + "' for user " + dn); continue; } - NamingEnumeration attributeRoles = roleAttribute.getAll(); - - while (attributeRoles.hasMore()) { - GrantedAuthority authority = createAuthority(attributeRoles.next()); + for (int j = 0; j < rolesForAttribute.length; j++) { + GrantedAuthority authority = createAuthority(rolesForAttribute[j]); if (authority != null) { essence.addAuthority(authority); - } else { - logger.debug("Failed to create an authority value from attribute with Id: " - + roleAttribute.getID()); } } } + //return essence.createUserDetails(); return essence; } @@ -94,8 +102,14 @@ public class LdapUserDetailsMapper implements LdapEntryMapper { * @param passwordAttribute the attribute instance containing the password * @return a String representation of the password. */ - protected String mapPassword(Attribute passwordAttribute) throws NamingException { - Object retrievedPassword = passwordAttribute.get(); + protected String mapPassword(Attribute passwordAttribute) { + Object retrievedPassword = null; + + try { + retrievedPassword = passwordAttribute.get(); + } catch (NamingException e) { + throw new UncategorizedLdapException("Failed to get password attribute", e); + } if (!(retrievedPassword instanceof String)) { // Assume it's binary @@ -106,6 +120,24 @@ public class LdapUserDetailsMapper implements LdapEntryMapper { } +// protected String mapUsername(DirContextAdapter ctx) { +// Attribute usernameAttribute = ctx.getAttributes().get(usernameAttributeName); +// String username; +// +// if (usernameAttribute == null) { +// throw new AttributesIntegrityViolationException( +// "Failed to get attribute " + usernameAttributeName + " from context"); +// } +// +// try { +// username = (String) usernameAttribute.get(); +// } catch (NamingException e) { +// throw new UncategorizedLdapException("Failed to get username from attribute " + usernameAttributeName, e); +// } +// +// return username; +// } + /** * Creates a GrantedAuthority from a role attribute. Override to customize * authority object creation. @@ -148,10 +180,15 @@ public class LdapUserDetailsMapper implements LdapEntryMapper { this.passwordAttributeName = passwordAttributeName; } + +// public void setUsernameAttributeName(String usernameAttributeName) { +// this.usernameAttributeName = usernameAttributeName; +// } + /** * The names of any attributes in the user's entry which represent application * roles. These will be converted to GrantedAuthoritys and added to the - * list in the returned LdapUserDetails object. + * list in the returned LdapUserDetails object. The attribute values must be Strings by default. * * @param roleAttributes the names of the role attributes. */ diff --git a/core/src/main/java/org/acegisecurity/userdetails/ldap/Person.java b/core/src/main/java/org/acegisecurity/userdetails/ldap/Person.java new file mode 100644 index 0000000000..a14b029996 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/ldap/Person.java @@ -0,0 +1,113 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acegisecurity.userdetails.ldap; + +import org.springframework.ldap.support.DirContextOperations; +import org.springframework.ldap.support.DirContextAdapter; +import org.springframework.util.Assert; +import org.acegisecurity.ldap.LdapUtils; + +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * UserDetails implementation whose properties are based on the LDAP schema for Person. + * + * @author Luke + * @since 2.0 + * @version $Id$ + */ +public class Person extends LdapUserDetailsImpl { + private String sn; + private List cn = new ArrayList(); + + protected Person() { + } + + public String getSn() { + return sn; + } + + public String[] getCn() { + return (String[]) cn.toArray(new String[cn.size()]); + } + + protected void populateContext(DirContextAdapter adapter) { + adapter.setAttributeValue("sn", sn); + adapter.setAttributeValues("cn", getCn()); + + if(getPassword() != null) { + adapter.setAttributeValue("userPassword", getPassword()); + } + adapter.setAttributeValues("objectclass", new String[] {"top", "person"}); + } + + public static class Essence extends LdapUserDetailsImpl.Essence { + + public Essence() { + } + + public Essence(DirContextOperations ctx) { + super(ctx); + setCn(ctx.getStringAttributes("cn")); + setSn(ctx.getStringAttribute("sn")); + Object passo = ctx.getObjectAttribute("userPassword"); + + if(passo != null) { + String password = LdapUtils.convertPasswordToString(passo); + setPassword(password); + } + } + + public Essence(Person copyMe) { + super(copyMe); + setSn(copyMe.sn); + ((Person) instance).cn = new ArrayList(copyMe.cn); + } + + protected LdapUserDetailsImpl createTarget() { + return new Person(); + } + + public void setSn(String sn) { + ((Person) instance).sn = sn; + } + + public void setCn(String[] cn) { + ((Person) instance).cn = Arrays.asList(cn); + } + + public void addCn(String value) { + ((Person) instance).cn.add(value); + } + + public LdapUserDetails createUserDetails() { + Person p = (Person) super.createUserDetails(); + Assert.hasLength(p.sn); + Assert.notNull(p.cn); + Assert.notEmpty(p.cn); + // TODO: Check contents for null entries + return p; + } + } +} + + + + + + + diff --git a/core/src/main/java/org/acegisecurity/userdetails/ldap/PersonContextMapper.java b/core/src/main/java/org/acegisecurity/userdetails/ldap/PersonContextMapper.java new file mode 100644 index 0000000000..a42b287c83 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/ldap/PersonContextMapper.java @@ -0,0 +1,31 @@ +package org.acegisecurity.userdetails.ldap; + +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.GrantedAuthority; +import org.springframework.ldap.support.DirContextOperations; +import org.springframework.ldap.support.DirContextAdapter; +import org.springframework.util.Assert; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class PersonContextMapper implements UserDetailsContextMapper { + + public UserDetails mapUserFromContext(DirContextOperations ctx, String username, GrantedAuthority[] authorities) { + Person.Essence p = new Person.Essence(ctx); + + p.setUsername(username); + p.setAuthorities(authorities); + + return p.createUserDetails(); + + } + + public void mapUserToContext(UserDetails user, DirContextAdapter ctx) { + Assert.isInstanceOf(Person.class, user, "UserDetails must be a Person instance"); + + Person p = (Person) user; + p.populateContext(ctx); + } +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/ldap/UserDetailsContextMapper.java b/core/src/main/java/org/acegisecurity/userdetails/ldap/UserDetailsContextMapper.java new file mode 100644 index 0000000000..d9894c851e --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/ldap/UserDetailsContextMapper.java @@ -0,0 +1,47 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acegisecurity.userdetails.ldap; + +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.GrantedAuthority; +import org.springframework.ldap.support.DirContextOperations; +import org.springframework.ldap.support.DirContextAdapter; + +/** + * Operations to map a UserDetails object to and from a Spring LDAP DirContextOperations implementation. + * Used by LdapUserDetailsManager when loading and saving/creating user information. + * + * @author Luke Taylor + * @since 2.0 + * @version $Id$ + */ +public interface UserDetailsContextMapper { + + /** + * Creates a fully populated UserDetails object for use by the security framework. + * + * @param ctx the context object which contains the user information. + * @param username the user's supplied login name. + * @param authority the list of authorities which the user should be given. + * @return the user object. + */ + UserDetails mapUserFromContext(DirContextOperations ctx, String username, GrantedAuthority[] authority); + + /** + * Reverse of the above operation. Populates a context object from the supplied user object. + * Called when saving a user, for example. + */ + void mapUserToContext(UserDetails user, DirContextAdapter ctx); +} diff --git a/core/src/test/java/org/acegisecurity/userdetails/ldap/InetOrgPersonTests.java b/core/src/test/java/org/acegisecurity/userdetails/ldap/InetOrgPersonTests.java new file mode 100644 index 0000000000..7b823a6779 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/userdetails/ldap/InetOrgPersonTests.java @@ -0,0 +1,65 @@ +package org.acegisecurity.userdetails.ldap; + +import junit.framework.TestCase; +import org.springframework.ldap.support.DirContextAdapter; +import org.springframework.ldap.support.DistinguishedName; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class InetOrgPersonTests extends TestCase { + + public void testUsernameIsMappedFromContextUidIfNotSet() { + InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext()); + InetOrgPerson p = (InetOrgPerson) essence.createUserDetails(); + + assertEquals("ghengis", p.getUsername()); + + } + + public void testUsernameIsDifferentFromContextUidIfSet() { + InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext()); + essence.setUsername("joe"); + InetOrgPerson p = (InetOrgPerson) essence.createUserDetails(); + + assertEquals("joe", p.getUsername()); + assertEquals("ghengis", p.getUid()); + } + + public void testAttributesMapCorrectlyFromContext() { + InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext()); + InetOrgPerson p = (InetOrgPerson) essence.createUserDetails(); + + assertEquals("ghengis@mongolia", p.getMail()); + assertEquals("Khan", p.getSn()); + assertEquals("Ghengis Khan", p.getCn()[0]); + assertEquals("00001", p.getEmployeeNumber()); + assertEquals("West", p.getDestinationIndicator()); + } + + public void testPasswordIsSetFromContextUserPassword() { + InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext()); + InetOrgPerson p = (InetOrgPerson) essence.createUserDetails(); + + assertEquals("pillage", p.getPassword()); + + } + + private DirContextAdapter createUserContext() { + DirContextAdapter ctx = new DirContextAdapter(); + + ctx.setDn(new DistinguishedName("ignored=ignored")); + ctx.setAttributeValue("uid", "ghengis"); + ctx.setAttributeValue("userPassword", "pillage"); + ctx.setAttributeValue("mail", "ghengis@mongolia"); + ctx.setAttributeValue("cn", "Ghengis Khan"); + ctx.setAttributeValue("sn", "Khan"); + ctx.setAttributeValue("employeeNumber", "00001"); + ctx.setAttributeValue("destinationIndicator", "West"); + ctx.setAttributeValue("o", "Hordes"); + + return ctx; + } + +} diff --git a/core/src/test/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsManagerTests.java b/core/src/test/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsManagerTests.java new file mode 100644 index 0000000000..f9ef9a5123 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsManagerTests.java @@ -0,0 +1,153 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acegisecurity.userdetails.ldap; + +import org.acegisecurity.ldap.AbstractLdapServerTestCase; +import org.acegisecurity.ldap.LdapUtils; +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.UsernameNotFoundException; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.GrantedAuthorityImpl; +import org.springframework.ldap.LdapTemplate; +import org.springframework.ldap.support.DirContextAdapter; +import org.springframework.ldap.support.DistinguishedName; +import org.springframework.dao.DataAccessException; + +import javax.naming.directory.DirContext; +import java.util.List; +import java.util.Iterator; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class LdapUserDetailsManagerTests extends AbstractLdapServerTestCase { + private static final GrantedAuthority[] TEST_AUTHORITIES = new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_CLOWNS"), + new GrantedAuthorityImpl("ROLE_ACROBATS")}; + private LdapUserDetailsManager mgr; + private LdapTemplate template; + + protected void onSetUp() { + mgr = new LdapUserDetailsManager(getInitialCtxFactory()); + template = new LdapTemplate(getInitialCtxFactory()); + DirContextAdapter ctx = new DirContextAdapter(); + + ctx.setAttributeValue("objectclass", "organizationalUnit"); + ctx.setAttributeValue("ou", "testpeople"); + template.bind("ou=testpeople", ctx, null); + + ctx.setAttributeValue("ou", "testgroups"); + template.bind("ou=testgroups", ctx, null); + + DirContextAdapter group = new DirContextAdapter(); + + group.setAttributeValue("objectclass", "groupOfNames"); + group.setAttributeValue("cn", "clowns"); + template.bind("cn=clowns,ou=testgroups", ctx, null); + + group.setAttributeValue("cn", "acrobats"); + template.bind("cn=acrobats,ou=testgroups", ctx, null); + + mgr.setUserDnBase("ou=testpeople"); + mgr.setGroupSearchBase("ou=testgroups"); + mgr.setGroupRoleAttributeName("cn"); + mgr.setGroupMemberAttributeName("member"); + mgr.setUserDetailsMapper(new PersonContextMapper()); + } + + + protected void tearDown() throws Exception { + Iterator people = template.list("ou=testpeople").iterator(); + + DirContext rootCtx = new DirContextAdapter(new DistinguishedName(getInitialCtxFactory().getRootDn())); + + while(people.hasNext()) { + template.unbind(LdapUtils.getRelativeName((String) people.next(), rootCtx)); + } + + template.unbind("ou=testpeople"); + template.unbind("cn=acrobats,ou=testgroups"); + template.unbind("cn=clowns,ou=testgroups"); + template.unbind("ou=testgroups"); + + } + + public void testLoadUserByUsernameReturnsCorrectData() { + mgr.setUserDnBase("ou=people"); + mgr.setGroupSearchBase("ou=groups"); + UserDetails bob = mgr.loadUserByUsername("bob"); + assertEquals("bob", bob.getUsername()); + // password isn't read + //assertEquals("bobspassword", bob.getPassword()); + + assertEquals(1, bob.getAuthorities().length); + } + + public void testLoadingInvalidUsernameThrowsUsernameNotFoundException() { + + try { + mgr.loadUserByUsername("jim"); + fail("Expected UsernameNotFoundException for user 'jim'"); + } catch(UsernameNotFoundException expected) { + // expected + } + } + + public void testUserExistsReturnsTrueForValidUser() { + mgr.setUserDnBase("ou=people"); + assertTrue(mgr.userExists("bob")); + } + + public void testUserExistsReturnsFalseForInValidUser() { + assertFalse(mgr.userExists("jim")); + } + + public void testCreateNewUserSucceeds() { + InetOrgPerson.Essence p = new InetOrgPerson.Essence(); + p.setCn(new String[] {"Joe Smeth"}); + p.setSn("Smeth"); + p.setUid("joe"); + p.setAuthorities(TEST_AUTHORITIES); + + mgr.createUser(p.createUserDetails()); + } + + public void testDeleteUserSucceeds() { + InetOrgPerson.Essence p = new InetOrgPerson.Essence(); + p.setCn(new String[] {"Don Smeth"}); + p.setSn("Smeth"); + p.setUid("don"); + p.setAuthorities(TEST_AUTHORITIES); + + mgr.createUser(p.createUserDetails()); + mgr.setUserDetailsMapper(new InetOrgPersonContextMapper()); + + InetOrgPerson don = (InetOrgPerson) mgr.loadUserByUsername("don"); + + assertEquals(2, don.getAuthorities().length); + + mgr.deleteUser("don"); + + try { + mgr.loadUserByUsername("don"); + fail("Expected UsernameNotFoundException after deleting user"); + } catch(UsernameNotFoundException expected) { + // expected + } + + // Check that no authorities are left + assertEquals(0, mgr.getUserAuthorities(mgr.buildDn("don"), "don").length); + } +} diff --git a/core/src/test/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsMapperTests.java b/core/src/test/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsMapperTests.java index d1203d7722..ee708328aa 100644 --- a/core/src/test/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsMapperTests.java +++ b/core/src/test/java/org/acegisecurity/userdetails/ldap/LdapUserDetailsMapperTests.java @@ -21,6 +21,8 @@ import javax.naming.directory.BasicAttributes; import javax.naming.directory.BasicAttribute; import org.acegisecurity.GrantedAuthorityImpl; +import org.springframework.ldap.support.DirContextAdapter; +import org.springframework.ldap.support.DistinguishedName; /** * Tests {@link LdapUserDetailsMapper}. @@ -38,14 +40,11 @@ public class LdapUserDetailsMapperTests extends TestCase { mapper.setRoleAttributes(new String[] {"userRole"}); - BasicAttributes attrs = new BasicAttributes(); - BasicAttribute roleAttribute = new BasicAttribute("userRole"); - roleAttribute.add("X"); - roleAttribute.add("Y"); - roleAttribute.add("Z"); - attrs.put(roleAttribute); + DirContextAdapter ctx = new DirContextAdapter(); - LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence) mapper.mapAttributes("cn=someName", attrs); + ctx.setAttributeValues("userRole", new String[] {"X", "Y", "Z"}); + + LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence) mapper.mapFromContext(ctx); assertEquals(3, user.getGrantedAuthorities().length); } @@ -61,24 +60,28 @@ public class LdapUserDetailsMapperTests extends TestCase { BasicAttributes attrs = new BasicAttributes(); attrs.put(new BasicAttribute("userRole", "x")); - LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence) mapper.mapAttributes("cn=someName", attrs); + DirContextAdapter ctx = new DirContextAdapter(attrs, new DistinguishedName("cn=someName")); + + LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence) mapper.mapFromContext(ctx); assertEquals(1, user.getGrantedAuthorities().length); assertEquals("ROLE_X", user.getGrantedAuthorities()[0].getAuthority()); } - public void testNonStringRoleAttributeIsIgnoredByDefault() throws Exception { - LdapUserDetailsMapper mapper = new LdapUserDetailsMapper(); - - mapper.setRoleAttributes(new String[] {"userRole"}); - - BasicAttributes attrs = new BasicAttributes(); - attrs.put(new BasicAttribute("userRole", new GrantedAuthorityImpl("X"))); - - LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence) mapper.mapAttributes("cn=someName", attrs); - - assertEquals(0, user.getGrantedAuthorities().length); - } +// public void testNonStringRoleAttributeIsIgnoredByDefault() throws Exception { +// LdapUserDetailsMapper mapper = new LdapUserDetailsMapper(); +// +// mapper.setRoleAttributes(new String[] {"userRole"}); +// +// BasicAttributes attrs = new BasicAttributes(); +// attrs.put(new BasicAttribute("userRole", new GrantedAuthorityImpl("X"))); +// +// DirContextAdapter ctx = new DirContextAdapter(attrs, new DistinguishedName("cn=someName")); +// +// LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence) mapper.mapFromContext(ctx); +// +// assertEquals(0, user.getGrantedAuthorities().length); +// } public void testPasswordAttributeIsMappedCorrectly() throws Exception { LdapUserDetailsMapper mapper = new LdapUserDetailsMapper(); @@ -87,8 +90,10 @@ public class LdapUserDetailsMapperTests extends TestCase { BasicAttributes attrs = new BasicAttributes(); attrs.put(new BasicAttribute("myappsPassword", "mypassword".getBytes())); + DirContextAdapter ctx = new DirContextAdapter(attrs, new DistinguishedName("cn=someName")); + LdapUserDetails user = - ((LdapUserDetailsImpl.Essence) mapper.mapAttributes("cn=someName", attrs)).createUserDetails(); + ((LdapUserDetailsImpl.Essence) mapper.mapFromContext(ctx)).createUserDetails(); assertEquals("mypassword", user.getPassword()); }