1 package org
.argeo
.security
.ldap
.jcr
;
3 import java
.security
.NoSuchAlgorithmException
;
4 import java
.security
.SecureRandom
;
5 import java
.util
.Arrays
;
6 import java
.util
.HashMap
;
8 import java
.util
.Random
;
9 import java
.util
.concurrent
.Executor
;
11 import javax
.jcr
.Node
;
12 import javax
.jcr
.Repository
;
13 import javax
.jcr
.RepositoryException
;
14 import javax
.jcr
.RepositoryFactory
;
15 import javax
.jcr
.Session
;
16 import javax
.jcr
.nodetype
.NodeType
;
18 import org
.apache
.commons
.logging
.Log
;
19 import org
.apache
.commons
.logging
.LogFactory
;
20 import org
.argeo
.ArgeoException
;
21 import org
.argeo
.jcr
.ArgeoJcrConstants
;
22 import org
.argeo
.jcr
.ArgeoNames
;
23 import org
.argeo
.jcr
.ArgeoTypes
;
24 import org
.argeo
.jcr
.JcrUtils
;
25 import org
.argeo
.security
.jcr
.JcrUserDetails
;
26 import org
.springframework
.ldap
.core
.DirContextAdapter
;
27 import org
.springframework
.ldap
.core
.DirContextOperations
;
28 import org
.springframework
.security
.GrantedAuthority
;
29 import org
.springframework
.security
.context
.SecurityContextHolder
;
30 import org
.springframework
.security
.providers
.encoding
.PasswordEncoder
;
31 import org
.springframework
.security
.userdetails
.UserDetails
;
32 import org
.springframework
.security
.userdetails
.ldap
.UserDetailsContextMapper
;
35 * Maps LDAP attributes and JCR properties. This class is meant to be robust,
36 * checks of which values should be mandatory should be performed at a higher
39 public class JcrUserDetailsContextMapper
implements UserDetailsContextMapper
,
41 private final static Log log
= LogFactory
42 .getLog(JcrUserDetailsContextMapper
.class);
44 private String usernameAttribute
;
45 private String passwordAttribute
;
46 private String homeBasePath
;
47 private String
[] userClasses
;
49 private Map
<String
, String
> propertyToAttributes
= new HashMap
<String
, String
>();
50 private Executor systemExecutor
;
51 private Session session
;
53 private PasswordEncoder passwordEncoder
;
54 private final Random random
;
56 public JcrUserDetailsContextMapper() {
57 random
= createRandom();
60 private static Random
createRandom() {
62 return SecureRandom
.getInstance("SHA1PRNG");
63 } catch (NoSuchAlgorithmException e
) {
64 return new Random(System
.currentTimeMillis());
68 public UserDetails
mapUserFromContext(final DirContextOperations ctx
,
69 final String username
, GrantedAuthority
[] authorities
) {
70 // if (repository == null)
71 // throw new ArgeoException("No JCR repository registered");
72 final StringBuffer userHomePathT
= new StringBuffer("");
73 Runnable action
= new Runnable() {
75 String userHomepath
= mapLdapToJcr(username
, ctx
);
76 userHomePathT
.append(userHomepath
);
79 if (SecurityContextHolder
.getContext().getAuthentication() == null)// authentication
80 systemExecutor
.execute(action
);
85 byte[] arr
= (byte[]) ctx
86 .getAttributeSortedStringSet(passwordAttribute
).first();
87 JcrUserDetails userDetails
= new JcrUserDetails(
88 userHomePathT
.toString(), username
, new String(arr
), true,
89 true, true, true, authorities
);
91 Arrays
.fill(arr
, (byte) 0);
95 /** @return path to the user home node */
96 protected String
mapLdapToJcr(String username
, DirContextOperations ctx
) {
97 // Session session = null;
99 // Repository nodeRepo = JcrUtils.getRepositoryByAlias(
100 // repositoryFactory, ArgeoJcrConstants.ALIAS_NODE);
101 // session = nodeRepo.login();
102 Node userHome
= JcrUtils
.getUserHome(session
, username
);
103 if (userHome
== null)
104 userHome
= createUserHome(session
, username
);
105 String userHomePath
= userHome
.getPath();
107 if (userHome
.hasNode(ARGEO_USER_PROFILE
)) {
108 userProfile
= userHome
.getNode(ARGEO_USER_PROFILE
);
110 userProfile
= userHome
.addNode(ARGEO_USER_PROFILE
);
111 userProfile
.addMixin(NodeType
.MIX_TITLE
);
112 userProfile
.addMixin(NodeType
.MIX_CREATED
);
113 userProfile
.addMixin(NodeType
.MIX_LAST_MODIFIED
);
115 for (String jcrProperty
: propertyToAttributes
.keySet())
116 ldapToJcr(userProfile
, jcrProperty
, ctx
);
118 if (log
.isDebugEnabled())
119 log
.debug("Mapped " + ctx
.getDn() + " to " + userProfile
);
121 } catch (RepositoryException e
) {
122 JcrUtils
.discardQuietly(session
);
123 throw new ArgeoException("Cannot synchronize JCR and LDAP", e
);
125 // JcrUtils.logoutQuietly(session);
129 protected Node
createUserHome(Session session
, String username
) {
131 Node userHome
= JcrUtils
.mkdirs(session
,
132 usernameToHomePath(username
));
133 userHome
.addMixin(ArgeoTypes
.ARGEO_USER_HOME
);
134 userHome
.setProperty(ARGEO_USER_ID
, username
);
136 } catch (RepositoryException e
) {
137 throw new ArgeoException("Cannot create home node for user "
142 protected String
usernameToHomePath(String username
) {
143 return homeBasePath
+ '/' + JcrUtils
.firstCharsToPath(username
, 2)
147 public void mapUserToContext(UserDetails user
, final DirContextAdapter ctx
) {
148 if (!(user
instanceof JcrUserDetails
))
149 throw new ArgeoException("Unsupported user details: "
152 ctx
.setAttributeValues("objectClass", userClasses
);
153 ctx
.setAttributeValue(usernameAttribute
, user
.getUsername());
154 ctx
.setAttributeValue(passwordAttribute
,
155 encodePassword(user
.getPassword()));
157 final JcrUserDetails jcrUserDetails
= (JcrUserDetails
) user
;
158 // systemExecutor.execute(new Runnable() {
159 // public void run() {
160 // Session session = null;
162 // Repository nodeRepo = JcrUtils.getRepositoryByAlias(
163 // repositoryFactory, ArgeoJcrConstants.ALIAS_NODE);
164 // session = nodeRepo.login();
165 Node userProfile
= session
.getNode(jcrUserDetails
.getHomePath()
166 + '/' + ARGEO_USER_PROFILE
);
167 for (String jcrProperty
: propertyToAttributes
.keySet())
168 jcrToLdap(userProfile
, jcrProperty
, ctx
);
169 if (log
.isDebugEnabled())
170 log
.debug("Mapped " + userProfile
+ " to " + ctx
.getDn());
171 } catch (RepositoryException e
) {
172 throw new ArgeoException("Cannot synchronize JCR and LDAP", e
);
180 protected String
encodePassword(String password
) {
181 if (!password
.startsWith("{")) {
182 byte[] salt
= new byte[16];
183 random
.nextBytes(salt
);
184 return passwordEncoder
.encodePassword(password
, salt
);
190 protected void ldapToJcr(Node userProfile
, String jcrProperty
,
191 DirContextOperations ctx
) {
193 String ldapAttribute
;
194 if (propertyToAttributes
.containsKey(jcrProperty
))
195 ldapAttribute
= propertyToAttributes
.get(jcrProperty
);
197 throw new ArgeoException(
198 "No LDAP attribute mapped for JCR proprty "
201 String value
= ctx
.getStringAttribute(ldapAttribute
);
204 userProfile
.setProperty(jcrProperty
, value
);
205 } catch (Exception e
) {
206 throw new ArgeoException("Cannot map JCR property " + jcrProperty
211 protected void jcrToLdap(Node userProfile
, String jcrProperty
,
212 DirContextOperations ctx
) {
214 if (!userProfile
.hasProperty(jcrProperty
))
216 String value
= userProfile
.getProperty(jcrProperty
).getString();
218 String ldapAttribute
;
219 if (propertyToAttributes
.containsKey(jcrProperty
))
220 ldapAttribute
= propertyToAttributes
.get(jcrProperty
);
222 throw new ArgeoException(
223 "No LDAP attribute mapped for JCR proprty "
225 ctx
.setAttributeValue(ldapAttribute
, value
);
226 } catch (Exception e
) {
227 throw new ArgeoException("Cannot map JCR property " + jcrProperty
232 public void setPropertyToAttributes(Map
<String
, String
> propertyToAttributes
) {
233 this.propertyToAttributes
= propertyToAttributes
;
236 public void setSystemExecutor(Executor systemExecutor
) {
237 this.systemExecutor
= systemExecutor
;
240 public void setHomeBasePath(String homeBasePath
) {
241 this.homeBasePath
= homeBasePath
;
244 // public void register(RepositoryFactory repositoryFactory,
245 // Map<String, String> parameters) {
246 // this.repositoryFactory = repositoryFactory;
249 // public void unregister(RepositoryFactory repositoryFactory,
250 // Map<String, String> parameters) {
251 // this.repositoryFactory = null;
254 public void setUsernameAttribute(String usernameAttribute
) {
255 this.usernameAttribute
= usernameAttribute
;
258 public void setPasswordAttribute(String passwordAttribute
) {
259 this.passwordAttribute
= passwordAttribute
;
262 public void setUserClasses(String
[] userClasses
) {
263 this.userClasses
= userClasses
;
266 public void setPasswordEncoder(PasswordEncoder passwordEncoder
) {
267 this.passwordEncoder
= passwordEncoder
;
270 public void setSession(Session session
) {
271 this.session
= session
;