]> git.argeo.org Git - lgpl/argeo-commons.git/blob - jcr/JcrUserDetailsContextMapper.java
Prepare next development cycle
[lgpl/argeo-commons.git] / 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.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
17 import org.apache.commons.logging.Log;
18 import org.apache.commons.logging.LogFactory;
19 import org.argeo.ArgeoException;
20 import org.argeo.jcr.ArgeoNames;
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 (ctx == null)
68 throw new ArgeoException("No LDAP information found for user "
69 + username);
70
71 final StringBuffer userHomePathT = new StringBuffer("");
72 Runnable action = new Runnable() {
73 public void run() {
74 String userHomepath = mapLdapToJcr(username, ctx);
75 userHomePathT.append(userHomepath);
76 }
77 };
78
79 if (SecurityContextHolder.getContext().getAuthentication() == null) {
80 // authentication
81 try {
82 systemExecutor.execute(action);
83 } finally {
84 JcrUtils.logoutQuietly(session);
85 }
86 } else {
87 // authenticated user
88 action.run();
89 }
90
91 // password
92 SortedSet<?> passwordAttributes = ctx
93 .getAttributeSortedStringSet(passwordAttribute);
94 String password;
95 if (passwordAttributes == null || passwordAttributes.size() == 0) {
96 throw new ArgeoException("No password found for user " + username);
97 } else {
98 byte[] arr = (byte[]) passwordAttributes.first();
99 password = new String(arr);
100 // erase password
101 Arrays.fill(arr, (byte) 0);
102 }
103 JcrUserDetails userDetails = new JcrUserDetails(
104 userHomePathT.toString(), username, password, true, true, true,
105 true, authorities);
106 return userDetails;
107 }
108
109 /** @return path to the user home node */
110 protected String mapLdapToJcr(String username, DirContextOperations ctx) {
111 try {
112 Node userHome = JcrUtils.getUserHome(session, username);
113 if (userHome == null)
114 userHome = JcrUtils.createUserHome(session, homeBasePath,
115 username);
116 String userHomePath = userHome.getPath();
117 Node userProfile; // = userHome.getNode(ARGEO_PROFILE);
118 if (userHome.hasNode(ARGEO_PROFILE)) {
119 userProfile = userHome.getNode(ARGEO_PROFILE);
120 } else {
121 throw new ArgeoException("We should never reach this point");
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 }