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