2 * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org
.argeo
.security
.ldap
;
19 import static org
.argeo
.security
.core
.ArgeoUserDetails
.createSimpleArgeoUser
;
21 import java
.security
.NoSuchAlgorithmException
;
22 import java
.security
.SecureRandom
;
23 import java
.util
.Collections
;
24 import java
.util
.List
;
25 import java
.util
.Random
;
27 import java
.util
.TreeSet
;
29 import javax
.naming
.Name
;
30 import javax
.naming
.NamingException
;
31 import javax
.naming
.directory
.DirContext
;
33 import org
.argeo
.ArgeoException
;
34 import org
.argeo
.security
.ArgeoUser
;
35 import org
.argeo
.security
.CurrentUserDao
;
36 import org
.argeo
.security
.SimpleArgeoUser
;
37 import org
.argeo
.security
.UserAdminDao
;
38 import org
.argeo
.security
.core
.ArgeoUserDetails
;
39 import org
.springframework
.ldap
.core
.ContextExecutor
;
40 import org
.springframework
.ldap
.core
.ContextMapper
;
41 import org
.springframework
.ldap
.core
.DirContextAdapter
;
42 import org
.springframework
.ldap
.core
.DistinguishedName
;
43 import org
.springframework
.ldap
.core
.LdapTemplate
;
44 import org
.springframework
.ldap
.core
.support
.BaseLdapPathContextSource
;
45 import org
.springframework
.security
.context
.SecurityContextHolder
;
46 import org
.springframework
.security
.ldap
.LdapUsernameToDnMapper
;
47 import org
.springframework
.security
.ldap
.LdapUtils
;
48 import org
.springframework
.security
.providers
.UsernamePasswordAuthenticationToken
;
49 import org
.springframework
.security
.providers
.encoding
.PasswordEncoder
;
50 import org
.springframework
.security
.userdetails
.UserDetails
;
51 import org
.springframework
.security
.userdetails
.UserDetailsManager
;
54 * Wraps a Spring LDAP user details manager, providing additional methods to
57 public class ArgeoSecurityDaoLdap
implements CurrentUserDao
, UserAdminDao
{
58 private String userBase
;
59 private String usernameAttribute
;
60 private String groupBase
;
61 private String
[] groupClasses
;
63 private String groupRoleAttribute
;
64 private String groupMemberAttribute
;
65 private String defaultRole
;
66 private String rolePrefix
;
68 private final LdapTemplate ldapTemplate
;
69 private final Random random
;
71 private LdapUsernameToDnMapper usernameMapper
;
72 private UserDetailsManager userDetailsManager
;
74 private PasswordEncoder passwordEncoder
;
77 * Standard constructor, using the LDAP context source shared with Spring
78 * Security components.
80 public ArgeoSecurityDaoLdap(BaseLdapPathContextSource contextSource
) {
81 this(new LdapTemplate(contextSource
), createRandom());
85 * Advanced constructor allowing to reuse an LDAP template and to explicitly
86 * set the random used as seed for SSHA password generation.
88 public ArgeoSecurityDaoLdap(LdapTemplate ldapTemplate
, Random random
) {
89 this.ldapTemplate
= ldapTemplate
;
93 private static Random
createRandom() {
95 return SecureRandom
.getInstance("SHA1PRNG");
96 } catch (NoSuchAlgorithmException e
) {
97 return new Random(System
.currentTimeMillis());
101 public synchronized void createUser(ArgeoUser user
) {
102 // normalize password
103 if (user
instanceof SimpleArgeoUser
) {
104 if (user
.getPassword() == null || user
.getPassword().equals(""))
105 ((SimpleArgeoUser
) user
).setPassword(encodePassword(user
107 else if (!user
.getPassword().startsWith("{"))
108 ((SimpleArgeoUser
) user
).setPassword(encodePassword(user
111 userDetailsManager
.createUser(new ArgeoUserDetails(user
));
114 public synchronized ArgeoUser
getUser(String uname
) {
115 SimpleArgeoUser user
= createSimpleArgeoUser(getDetails(uname
));
116 user
.setPassword(null);
120 public synchronized ArgeoUser
getUserWithPassword(String uname
) {
121 return createSimpleArgeoUser(getDetails(uname
));
124 @SuppressWarnings("unchecked")
125 public synchronized Set
<ArgeoUser
> listUsers() {
126 List
<String
> usernames
= (List
<String
>) ldapTemplate
.listBindings(
127 new DistinguishedName(userBase
), new ContextMapper() {
128 public Object
mapFromContext(Object ctxArg
) {
129 DirContextAdapter ctx
= (DirContextAdapter
) ctxArg
;
130 return ctx
.getStringAttribute(usernameAttribute
);
134 TreeSet
<ArgeoUser
> lst
= new TreeSet
<ArgeoUser
>();
135 for (String username
: usernames
) {
136 lst
.add(createSimpleArgeoUser(getDetails(username
)));
138 return Collections
.unmodifiableSortedSet(lst
);
141 @SuppressWarnings("unchecked")
142 public Set
<String
> listEditableRoles() {
143 return Collections
.unmodifiableSortedSet(new TreeSet
<String
>(
144 ldapTemplate
.listBindings(groupBase
, new ContextMapper() {
145 public Object
mapFromContext(Object ctxArg
) {
146 String groupName
= ((DirContextAdapter
) ctxArg
)
147 .getStringAttribute(groupRoleAttribute
);
148 String roleName
= convertGroupToRole(groupName
);
154 @SuppressWarnings("unchecked")
155 public Set
<ArgeoUser
> listUsersInRole(String role
) {
156 return (Set
<ArgeoUser
>) ldapTemplate
.lookup(
157 buildGroupDn(convertRoleToGroup(role
)), new ContextMapper() {
158 public Object
mapFromContext(Object ctxArg
) {
159 DirContextAdapter ctx
= (DirContextAdapter
) ctxArg
;
160 String
[] userDns
= ctx
161 .getStringAttributes(groupMemberAttribute
);
162 TreeSet
<ArgeoUser
> set
= new TreeSet
<ArgeoUser
>();
163 for (String userDn
: userDns
) {
164 DistinguishedName dn
= new DistinguishedName(userDn
);
165 String username
= dn
.getValue(usernameAttribute
);
166 set
.add(createSimpleArgeoUser(getDetails(username
)));
168 return Collections
.unmodifiableSortedSet(set
);
173 public synchronized void updateUser(ArgeoUser user
) {
174 // normalize password
175 String password
= user
.getPassword();
176 if (password
== null)
177 password
= getUserWithPassword(user
.getUsername()).getPassword();
178 if (!password
.startsWith("{"))
179 password
= encodePassword(user
.getPassword());
180 SimpleArgeoUser simpleArgeoUser
= new SimpleArgeoUser(user
);
181 simpleArgeoUser
.setPassword(password
);
183 ArgeoUserDetails argeoUserDetails
= new ArgeoUserDetails(user
);
184 userDetailsManager
.updateUser(new ArgeoUserDetails(user
));
185 // refresh logged in user
186 if (ArgeoUserDetails
.securityContextUser().getUsername()
187 .equals(argeoUserDetails
.getUsername())) {
188 SecurityContextHolder
.getContext().setAuthentication(
189 new UsernamePasswordAuthenticationToken(argeoUserDetails
,
190 null, argeoUserDetails
.getAuthorities()));
194 public void updateCurrentUserPassword(String oldPassword
, String newPassword
) {
195 SimpleArgeoUser user
= new SimpleArgeoUser(
196 ArgeoUserDetails
.securityContextUser());
197 if (!passwordEncoder
.isPasswordValid(user
.getPassword(), oldPassword
,
199 throw new ArgeoException("Old password is not correct.");
200 user
.setPassword(encodePassword(newPassword
));
204 public void updateUserPassword(String username
, String password
) {
205 SimpleArgeoUser user
= new SimpleArgeoUser(getUser(username
));
206 user
.setPassword(encodePassword(password
));
210 protected String
encodePassword(String password
) {
211 byte[] salt
= new byte[16];
212 random
.nextBytes(salt
);
213 return passwordEncoder
.encodePassword(password
, salt
);
216 public synchronized void deleteUser(String username
) {
217 userDetailsManager
.deleteUser(username
);
220 public synchronized Boolean
userExists(String username
) {
221 return userDetailsManager
.userExists(username
);
224 public void createRole(String role
, final String superuserName
) {
225 String group
= convertRoleToGroup(role
);
226 DistinguishedName superuserDn
= (DistinguishedName
) ldapTemplate
227 .executeReadWrite(new ContextExecutor() {
228 public Object
executeWithContext(DirContext ctx
)
229 throws NamingException
{
230 return LdapUtils
.getFullDn(
231 usernameMapper
.buildDn(superuserName
), ctx
);
235 Name groupDn
= buildGroupDn(group
);
236 DirContextAdapter context
= new DirContextAdapter();
237 context
.setAttributeValues("objectClass", groupClasses
);
238 context
.setAttributeValue("cn", group
);
239 // Add superuser because cannot create empty group
240 context
.setAttributeValue(groupMemberAttribute
, superuserDn
.toString());
241 ldapTemplate
.bind(groupDn
, context
, null);
244 public void deleteRole(String role
) {
245 String group
= convertRoleToGroup(role
);
246 Name dn
= buildGroupDn(group
);
247 ldapTemplate
.unbind(dn
);
250 /** Maps a role (ROLE_XXX) to the related LDAP group (xxx) */
251 protected String
convertRoleToGroup(String role
) {
253 if (group
.startsWith(rolePrefix
)) {
254 group
= group
.substring(rolePrefix
.length());
255 group
= group
.toLowerCase();
260 /** Maps anLDAP group (xxx) to the related role (ROLE_XXX) */
261 protected String
convertGroupToRole(String groupName
) {
262 groupName
= groupName
.toUpperCase();
264 return rolePrefix
+ groupName
;
267 protected Name
buildGroupDn(String name
) {
268 return new DistinguishedName(groupRoleAttribute
+ "=" + name
+ ","
272 public void setUserDetailsManager(UserDetailsManager userDetailsManager
) {
273 this.userDetailsManager
= userDetailsManager
;
276 public void setUserBase(String userBase
) {
277 this.userBase
= userBase
;
280 public void setUsernameAttribute(String usernameAttribute
) {
281 this.usernameAttribute
= usernameAttribute
;
284 protected UserDetails
getDetails(String username
) {
285 return userDetailsManager
.loadUserByUsername(username
);
288 public void setGroupBase(String groupBase
) {
289 this.groupBase
= groupBase
;
292 public void setGroupRoleAttribute(String groupRoleAttributeName
) {
293 this.groupRoleAttribute
= groupRoleAttributeName
;
296 public void setGroupMemberAttribute(String groupMemberAttributeName
) {
297 this.groupMemberAttribute
= groupMemberAttributeName
;
300 public void setDefaultRole(String defaultRole
) {
301 this.defaultRole
= defaultRole
;
304 public void setRolePrefix(String rolePrefix
) {
305 this.rolePrefix
= rolePrefix
;
308 public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper
) {
309 this.usernameMapper
= usernameMapper
;
312 public String
getDefaultRole() {
316 public void setGroupClasses(String
[] groupClasses
) {
317 this.groupClasses
= groupClasses
;
320 public void setPasswordEncoder(PasswordEncoder passwordEncoder
) {
321 this.passwordEncoder
= passwordEncoder
;