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
.ArrayList
;
24 import java
.util
.List
;
25 import java
.util
.Random
;
27 import javax
.naming
.Name
;
28 import javax
.naming
.NamingException
;
29 import javax
.naming
.directory
.DirContext
;
31 import org
.argeo
.security
.ArgeoSecurityDao
;
32 import org
.argeo
.security
.ArgeoUser
;
33 import org
.argeo
.security
.SimpleArgeoUser
;
34 import org
.argeo
.security
.core
.ArgeoUserDetails
;
35 import org
.springframework
.beans
.factory
.InitializingBean
;
36 import org
.springframework
.ldap
.core
.ContextExecutor
;
37 import org
.springframework
.ldap
.core
.ContextMapper
;
38 import org
.springframework
.ldap
.core
.DirContextAdapter
;
39 import org
.springframework
.ldap
.core
.DistinguishedName
;
40 import org
.springframework
.ldap
.core
.LdapTemplate
;
41 import org
.springframework
.ldap
.core
.support
.BaseLdapPathContextSource
;
42 import org
.springframework
.security
.context
.SecurityContextHolder
;
43 import org
.springframework
.security
.ldap
.DefaultLdapUsernameToDnMapper
;
44 import org
.springframework
.security
.ldap
.LdapAuthoritiesPopulator
;
45 import org
.springframework
.security
.ldap
.LdapUsernameToDnMapper
;
46 import org
.springframework
.security
.ldap
.LdapUtils
;
47 import org
.springframework
.security
.ldap
.populator
.DefaultLdapAuthoritiesPopulator
;
48 import org
.springframework
.security
.ldap
.search
.FilterBasedLdapUserSearch
;
49 import org
.springframework
.security
.providers
.UsernamePasswordAuthenticationToken
;
50 import org
.springframework
.security
.providers
.ldap
.authenticator
.LdapShaPasswordEncoder
;
51 import org
.springframework
.security
.userdetails
.UserDetails
;
52 import org
.springframework
.security
.userdetails
.UserDetailsManager
;
53 import org
.springframework
.security
.userdetails
.UserDetailsService
;
54 import org
.springframework
.security
.userdetails
.ldap
.LdapUserDetailsManager
;
55 import org
.springframework
.security
.userdetails
.ldap
.LdapUserDetailsService
;
56 import org
.springframework
.security
.userdetails
.ldap
.UserDetailsContextMapper
;
58 public class ArgeoSecurityDaoLdap
implements ArgeoSecurityDao
, InitializingBean
{
59 // private final static Log log = LogFactory.getLog(UserDaoLdap.class);
61 private UserDetailsManager userDetailsManager
;
62 private LdapAuthoritiesPopulator authoritiesPopulator
;
63 private String userBase
= "ou=People";
64 private String usernameAttributeName
= "uid";
65 private String groupBase
= "ou=Roles";
66 private String
[] groupClasses
= { "top", "groupOfNames" };
67 private String groupRoleAttributeName
= "cn";
68 private String groupMemberAttributeName
= "member";
69 private String defaultRole
= "ROLE_USER";
70 private String rolePrefix
= "ROLE_";
72 private final BaseLdapPathContextSource contextSource
;
73 private final LdapTemplate ldapTemplate
;
75 private LdapUsernameToDnMapper usernameMapper
= null;
77 private UserDetailsContextMapper userDetailsMapper
;
78 private LdapUserDetailsService ldapUserDetailsService
;
79 private List
<UserNatureMapper
> userNatureMappers
;
81 private LdapShaPasswordEncoder ldapShaPasswordEncoder
= new LdapShaPasswordEncoder();
82 private Random random
;
84 public ArgeoSecurityDaoLdap(BaseLdapPathContextSource contextSource
) {
85 this.contextSource
= contextSource
;
86 ldapTemplate
= new LdapTemplate(this.contextSource
);
88 random
= SecureRandom
.getInstance("SHA1PRNG");
89 } catch (NoSuchAlgorithmException e
) {
90 random
= new Random(System
.currentTimeMillis());
94 public void afterPropertiesSet() throws Exception
{
95 if (usernameMapper
== null)
96 usernameMapper
= new DefaultLdapUsernameToDnMapper(userBase
,
97 usernameAttributeName
);
99 if (authoritiesPopulator
== null) {
100 DefaultLdapAuthoritiesPopulator ap
= new DefaultLdapAuthoritiesPopulator(
101 ldapTemplate
.getContextSource(), groupBase
);
102 ap
.setDefaultRole(defaultRole
);
103 ap
.setGroupSearchFilter(groupMemberAttributeName
+ "={0}");
104 authoritiesPopulator
= ap
;
107 if (userDetailsMapper
== null) {
108 ArgeoUserDetailsContextMapper audm
= new ArgeoUserDetailsContextMapper();
109 audm
.setUserNatureMappers(userNatureMappers
);
110 userDetailsMapper
= audm
;
113 if (userDetailsManager
== null) {
114 LdapUserDetailsManager ludm
= new LdapUserDetailsManager(
115 ldapTemplate
.getContextSource());
116 ludm
.setGroupSearchBase(groupBase
);
117 ludm
.setUserDetailsMapper(userDetailsMapper
);
118 ludm
.setUsernameMapper(usernameMapper
);
119 ludm
.setGroupMemberAttributeName(groupMemberAttributeName
);
120 userDetailsManager
= ludm
;
123 if (ldapUserDetailsService
== null) {
124 FilterBasedLdapUserSearch ldapUserSearch
= new FilterBasedLdapUserSearch(
125 userBase
, "(" + usernameAttributeName
+ "={0})",
127 ldapUserDetailsService
= new LdapUserDetailsService(ldapUserSearch
,
128 authoritiesPopulator
);
129 ldapUserDetailsService
.setUserDetailsMapper(userDetailsMapper
);
133 public synchronized void create(ArgeoUser user
) {
134 userDetailsManager
.createUser(new ArgeoUserDetails(user
));
137 public synchronized ArgeoUser
getUser(String uname
) {
138 SimpleArgeoUser user
= createSimpleArgeoUser(getDetails(uname
));
139 user
.setPassword(null);
143 public synchronized ArgeoUser
getUserWithPassword(String uname
) {
144 return createSimpleArgeoUser(getDetails(uname
));
147 // public ArgeoUser getCurrentUser() {
148 // ArgeoUser argeoUser = ArgeoUserDetails.securityContextUser();
149 // if (argeoUser == null)
151 // if (argeoUser.getRoles().contains(defaultRole))
152 // argeoUser.getRoles().remove(defaultRole);
156 @SuppressWarnings("unchecked")
157 public synchronized List
<ArgeoUser
> listUsers() {
158 List
<String
> usernames
= (List
<String
>) ldapTemplate
.listBindings(
159 new DistinguishedName(userBase
), new ContextMapper() {
160 public Object
mapFromContext(Object ctxArg
) {
161 DirContextAdapter ctx
= (DirContextAdapter
) ctxArg
;
162 return ctx
.getStringAttribute(usernameAttributeName
);
166 List
<ArgeoUser
> lst
= new ArrayList
<ArgeoUser
>();
167 for (String username
: usernames
) {
168 lst
.add(createSimpleArgeoUser(getDetails(username
)));
173 @SuppressWarnings("unchecked")
174 public List
<String
> listEditableRoles() {
175 return (List
<String
>) ldapTemplate
.listBindings(groupBase
,
176 new ContextMapper() {
177 public Object
mapFromContext(Object ctxArg
) {
178 String groupName
= ((DirContextAdapter
) ctxArg
)
179 .getStringAttribute(groupRoleAttributeName
);
180 String roleName
= convertGroupToRole(groupName
);
186 @SuppressWarnings("unchecked")
187 public List
<ArgeoUser
> listUsersInRole(String role
) {
188 return (List
<ArgeoUser
>) ldapTemplate
.lookup(
189 buildGroupDn(convertRoleToGroup(role
)), new ContextMapper() {
190 public Object
mapFromContext(Object ctxArg
) {
191 DirContextAdapter ctx
= (DirContextAdapter
) ctxArg
;
192 String
[] userDns
= ctx
193 .getStringAttributes(groupMemberAttributeName
);
194 List
<ArgeoUser
> lst
= new ArrayList
<ArgeoUser
>();
195 for (String userDn
: userDns
) {
196 DistinguishedName dn
= new DistinguishedName(userDn
);
198 .getValue(usernameAttributeName
);
199 lst
.add(createSimpleArgeoUser(getDetails(username
)));
206 public synchronized void update(ArgeoUser user
) {
207 ArgeoUserDetails argeoUserDetails
= new ArgeoUserDetails(user
);
208 userDetailsManager
.updateUser(new ArgeoUserDetails(user
));
209 // refresh logged in user
210 if (ArgeoUserDetails
.securityContextUser().getUsername()
211 .equals(argeoUserDetails
.getUsername())) {
212 SecurityContextHolder
.getContext().setAuthentication(
213 new UsernamePasswordAuthenticationToken(argeoUserDetails
,
214 null, argeoUserDetails
.getAuthorities()));
218 public synchronized void delete(String username
) {
219 userDetailsManager
.deleteUser(username
);
222 public synchronized Boolean
userExists(String username
) {
223 return userDetailsManager
.userExists(username
);
226 public void createRole(String role
, final String superuserName
) {
227 String group
= convertRoleToGroup(role
);
228 DistinguishedName superuserDn
= (DistinguishedName
) ldapTemplate
229 .executeReadWrite(new ContextExecutor() {
230 public Object
executeWithContext(DirContext ctx
)
231 throws NamingException
{
232 return LdapUtils
.getFullDn(
233 usernameMapper
.buildDn(superuserName
), ctx
);
237 Name groupDn
= buildGroupDn(group
);
238 DirContextAdapter context
= new DirContextAdapter();
239 context
.setAttributeValues("objectClass", groupClasses
);
240 context
.setAttributeValue("cn", group
);
242 // Add superuser because cannot create empty group
243 context
.setAttributeValue(groupMemberAttributeName
,
244 superuserDn
.toString());
246 ldapTemplate
.bind(groupDn
, context
, null);
249 public void deleteRole(String role
) {
250 String group
= convertRoleToGroup(role
);
251 Name dn
= buildGroupDn(group
);
252 ldapTemplate
.unbind(dn
);
255 public Boolean
isPasswordValid(String encoded
, String raw
) {
256 return ldapShaPasswordEncoder
.isPasswordValid(encoded
, raw
, null);
259 public String
encodePassword(String raw
) {
261 // TODO: check that Linux auth supports SSHA
262 // byte[] salt = new byte[16];
263 // random.nextBytes(salt);
264 return ldapShaPasswordEncoder
.encodePassword(raw
, salt
);
267 protected String
convertRoleToGroup(String role
) {
269 if (group
.startsWith(rolePrefix
)) {
270 group
= group
.substring(rolePrefix
.length());
271 group
= group
.toLowerCase();
276 public String
convertGroupToRole(String groupName
) {
277 groupName
= groupName
.toUpperCase();
279 return rolePrefix
+ groupName
;
282 protected Name
buildGroupDn(String name
) {
283 return new DistinguishedName(groupRoleAttributeName
+ "=" + name
+ ","
287 public void setUserDetailsManager(UserDetailsManager userDetailsManager
) {
288 this.userDetailsManager
= userDetailsManager
;
291 public void setUserBase(String userBase
) {
292 this.userBase
= userBase
;
295 public void setUsernameAttributeName(String usernameAttribute
) {
296 this.usernameAttributeName
= usernameAttribute
;
299 public void setAuthoritiesPopulator(
300 LdapAuthoritiesPopulator authoritiesPopulator
) {
301 this.authoritiesPopulator
= authoritiesPopulator
;
304 protected UserDetails
getDetails(String username
) {
305 return userDetailsManager
.loadUserByUsername(username
);
308 public void setGroupBase(String groupBase
) {
309 this.groupBase
= groupBase
;
312 public void setGroupRoleAttributeName(String groupRoleAttributeName
) {
313 this.groupRoleAttributeName
= groupRoleAttributeName
;
316 public void setGroupMemberAttributeName(String groupMemberAttributeName
) {
317 this.groupMemberAttributeName
= groupMemberAttributeName
;
320 public void setDefaultRole(String defaultRole
) {
321 this.defaultRole
= defaultRole
;
324 public void setRolePrefix(String rolePrefix
) {
325 this.rolePrefix
= rolePrefix
;
328 public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper
) {
329 this.usernameMapper
= usernameMapper
;
332 public void setUserDetailsMapper(UserDetailsContextMapper userDetailsMapper
) {
333 this.userDetailsMapper
= userDetailsMapper
;
336 public LdapAuthoritiesPopulator
getAuthoritiesPopulator() {
337 return authoritiesPopulator
;
340 public UserDetailsContextMapper
getUserDetailsMapper() {
341 return userDetailsMapper
;
344 public void setUserNatureMappers(List
<UserNatureMapper
> userNatureMappers
) {
345 this.userNatureMappers
= userNatureMappers
;
348 public String
getDefaultRole() {
352 public void setGroupClasses(String
[] groupClasses
) {
353 this.groupClasses
= groupClasses
;
356 public UserDetailsService
getUserDetailsService() {
357 return ldapUserDetailsService
;