]> git.argeo.org Git - lgpl/argeo-commons.git/blob - security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java
Update SEBI :
[lgpl/argeo-commons.git] / security / runtime / org.argeo.security.ldap / src / main / java / org / argeo / security / ldap / jcr / JcrUserDetailsContextMapper.java
1 package org.argeo.security.ldap.jcr;
2
3 import java.security.NoSuchAlgorithmException;
4 import java.security.SecureRandom;
5 import java.util.Arrays;
6 import java.util.HashMap;
7 import java.util.Map;
8 import java.util.Random;
9 import java.util.concurrent.Executor;
10
11 import javax.jcr.Node;
12 import javax.jcr.RepositoryException;
13 import javax.jcr.Session;
14 import javax.jcr.nodetype.NodeType;
15
16 import org.apache.commons.logging.Log;
17 import org.apache.commons.logging.LogFactory;
18 import org.argeo.ArgeoException;
19 import org.argeo.jcr.ArgeoNames;
20 import org.argeo.jcr.ArgeoTypes;
21 import org.argeo.jcr.JcrUtils;
22 import org.argeo.security.jcr.JcrUserDetails;
23 import org.springframework.ldap.core.DirContextAdapter;
24 import org.springframework.ldap.core.DirContextOperations;
25 import org.springframework.security.GrantedAuthority;
26 import org.springframework.security.context.SecurityContextHolder;
27 import org.springframework.security.providers.encoding.PasswordEncoder;
28 import org.springframework.security.userdetails.UserDetails;
29 import org.springframework.security.userdetails.ldap.UserDetailsContextMapper;
30
31 /**
32 * Maps LDAP attributes and JCR properties. This class is meant to be robust,
33 * checks of which values should be mandatory should be performed at a higher
34 * level.
35 */
36 public class JcrUserDetailsContextMapper implements UserDetailsContextMapper,
37 ArgeoNames {
38 private final static Log log = LogFactory
39 .getLog(JcrUserDetailsContextMapper.class);
40
41 private String usernameAttribute;
42 private String passwordAttribute;
43 private String homeBasePath;
44 private String[] userClasses;
45
46 private Map<String, String> propertyToAttributes = new HashMap<String, String>();
47 private Executor systemExecutor;
48 private Session session;
49
50 private PasswordEncoder passwordEncoder;
51 private final Random random;
52
53 public JcrUserDetailsContextMapper() {
54 random = createRandom();
55 }
56
57 private static Random createRandom() {
58 try {
59 return SecureRandom.getInstance("SHA1PRNG");
60 } catch (NoSuchAlgorithmException e) {
61 return new Random(System.currentTimeMillis());
62 }
63 }
64
65 public UserDetails mapUserFromContext(final DirContextOperations ctx,
66 final String username, GrantedAuthority[] authorities) {
67 // if (repository == null)
68 // throw new ArgeoException("No JCR repository registered");
69 final StringBuffer userHomePathT = new StringBuffer("");
70 Runnable action = new Runnable() {
71 public void run() {
72 String userHomepath = mapLdapToJcr(username, ctx);
73 userHomePathT.append(userHomepath);
74 }
75 };
76
77 if (SecurityContextHolder.getContext().getAuthentication() == null) {
78 // authentication
79 systemExecutor.execute(action);
80 JcrUtils.logoutQuietly(session);
81 } else {
82 // authenticated user
83 action.run();
84 }
85
86 // password
87 byte[] arr = (byte[]) ctx
88 .getAttributeSortedStringSet(passwordAttribute).first();
89 JcrUserDetails userDetails = new JcrUserDetails(
90 userHomePathT.toString(), username, new String(arr), true,
91 true, true, true, authorities);
92 // erase password
93 Arrays.fill(arr, (byte) 0);
94 return userDetails;
95 }
96
97 /** @return path to the user home node */
98 protected String mapLdapToJcr(String username, DirContextOperations ctx) {
99 // Session session = null;
100 try {
101 // Repository nodeRepo = JcrUtils.getRepositoryByAlias(
102 // repositoryFactory, ArgeoJcrConstants.ALIAS_NODE);
103 // session = nodeRepo.login();
104 Node userHome = JcrUtils.getUserHome(session, username);
105 if (userHome == null)
106 userHome = createUserHome(session, username);
107 String userHomePath = userHome.getPath();
108 Node userProfile;
109 if (userHome.hasNode(ARGEO_USER_PROFILE)) {
110 userProfile = userHome.getNode(ARGEO_USER_PROFILE);
111 } else {
112 userProfile = userHome.addNode(ARGEO_USER_PROFILE);
113 userProfile.addMixin(NodeType.MIX_TITLE);
114 userProfile.addMixin(NodeType.MIX_CREATED);
115 userProfile.addMixin(NodeType.MIX_LAST_MODIFIED);
116 }
117 for (String jcrProperty : propertyToAttributes.keySet())
118 ldapToJcr(userProfile, jcrProperty, ctx);
119 session.save();
120 if (log.isDebugEnabled())
121 log.debug("Mapped " + ctx.getDn() + " to " + userProfile);
122 return userHomePath;
123 } catch (RepositoryException e) {
124 JcrUtils.discardQuietly(session);
125 throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
126 } finally {
127 // JcrUtils.logoutQuietly(session);
128 }
129 }
130
131 protected Node createUserHome(Session session, String username) {
132 try {
133 Node userHome = JcrUtils.mkdirs(session,
134 usernameToHomePath(username));
135 userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
136 userHome.setProperty(ARGEO_USER_ID, username);
137 return userHome;
138 } catch (RepositoryException e) {
139 throw new ArgeoException("Cannot create home node for user "
140 + username, e);
141 }
142 }
143
144 protected String usernameToHomePath(String username) {
145 return homeBasePath + '/' + JcrUtils.firstCharsToPath(username, 2)
146 + '/' + username;
147 }
148
149 public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
150 if (!(user instanceof JcrUserDetails))
151 throw new ArgeoException("Unsupported user details: "
152 + user.getClass());
153
154 ctx.setAttributeValues("objectClass", userClasses);
155 ctx.setAttributeValue(usernameAttribute, user.getUsername());
156 ctx.setAttributeValue(passwordAttribute,
157 encodePassword(user.getPassword()));
158
159 final JcrUserDetails jcrUserDetails = (JcrUserDetails) user;
160 // systemExecutor.execute(new Runnable() {
161 // public void run() {
162 // Session session = null;
163 try {
164 // Repository nodeRepo = JcrUtils.getRepositoryByAlias(
165 // repositoryFactory, ArgeoJcrConstants.ALIAS_NODE);
166 // session = nodeRepo.login();
167 Node userProfile = session.getNode(jcrUserDetails.getHomePath()
168 + '/' + ARGEO_USER_PROFILE);
169 for (String jcrProperty : propertyToAttributes.keySet())
170 jcrToLdap(userProfile, jcrProperty, ctx);
171 if (log.isDebugEnabled())
172 log.debug("Mapped " + userProfile + " to " + ctx.getDn());
173 } catch (RepositoryException e) {
174 throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
175 } finally {
176 // session.logout();
177 }
178 // }
179 // });
180 }
181
182 protected String encodePassword(String password) {
183 if (!password.startsWith("{")) {
184 byte[] salt = new byte[16];
185 random.nextBytes(salt);
186 return passwordEncoder.encodePassword(password, salt);
187 } else {
188 return password;
189 }
190 }
191
192 protected void ldapToJcr(Node userProfile, String jcrProperty,
193 DirContextOperations ctx) {
194 try {
195 String ldapAttribute;
196 if (propertyToAttributes.containsKey(jcrProperty))
197 ldapAttribute = propertyToAttributes.get(jcrProperty);
198 else
199 throw new ArgeoException(
200 "No LDAP attribute mapped for JCR proprty "
201 + jcrProperty);
202
203 String value = ctx.getStringAttribute(ldapAttribute);
204 if (value == null)
205 return;
206 userProfile.setProperty(jcrProperty, value);
207 } catch (Exception e) {
208 throw new ArgeoException("Cannot map JCR property " + jcrProperty
209 + " from LDAP", e);
210 }
211 }
212
213 protected void jcrToLdap(Node userProfile, String jcrProperty,
214 DirContextOperations ctx) {
215 try {
216 if (!userProfile.hasProperty(jcrProperty))
217 return;
218 String value = userProfile.getProperty(jcrProperty).getString();
219
220 String ldapAttribute;
221 if (propertyToAttributes.containsKey(jcrProperty))
222 ldapAttribute = propertyToAttributes.get(jcrProperty);
223 else
224 throw new ArgeoException(
225 "No LDAP attribute mapped for JCR proprty "
226 + jcrProperty);
227 ctx.setAttributeValue(ldapAttribute, value);
228 } catch (Exception e) {
229 throw new ArgeoException("Cannot map JCR property " + jcrProperty
230 + " from LDAP", e);
231 }
232 }
233
234 public void setPropertyToAttributes(Map<String, String> propertyToAttributes) {
235 this.propertyToAttributes = propertyToAttributes;
236 }
237
238 public void setSystemExecutor(Executor systemExecutor) {
239 this.systemExecutor = systemExecutor;
240 }
241
242 public void setHomeBasePath(String homeBasePath) {
243 this.homeBasePath = homeBasePath;
244 }
245
246 // public void register(RepositoryFactory repositoryFactory,
247 // Map<String, String> parameters) {
248 // this.repositoryFactory = repositoryFactory;
249 // }
250 //
251 // public void unregister(RepositoryFactory repositoryFactory,
252 // Map<String, String> parameters) {
253 // this.repositoryFactory = null;
254 // }
255
256 public void setUsernameAttribute(String usernameAttribute) {
257 this.usernameAttribute = usernameAttribute;
258 }
259
260 public void setPasswordAttribute(String passwordAttribute) {
261 this.passwordAttribute = passwordAttribute;
262 }
263
264 public void setUserClasses(String[] userClasses) {
265 this.userClasses = userClasses;
266 }
267
268 public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
269 this.passwordEncoder = passwordEncoder;
270 }
271
272 public void setSession(Session session) {
273 this.session = session;
274 }
275
276 }