]> git.argeo.org Git - lgpl/argeo-commons.git/blob - ldap/jcr/JcrUserDetailsContextMapper.java
Prepare next development cycle
[lgpl/argeo-commons.git] / 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.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.BadCredentialsException;
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 String usernameLdap = ctx.getStringAttribute(usernameAttribute);
113 // log.debug("username=" + username + ", usernameLdap=" + usernameLdap);
114 if (!username.equals(usernameLdap)) {
115 String msg = "Provided username '" + username
116 + "' is different from username stored in LDAP '"
117 + usernameLdap+"'";
118 // we log it because the exception may not be displayed
119 log.error(msg);
120 throw new BadCredentialsException(msg);
121 }
122
123 try {
124
125 Node userHome = JcrUtils.getUserHome(session, username);
126 if (userHome == null)
127 userHome = JcrUtils.createUserHome(session, homeBasePath,
128 username);
129 String userHomePath = userHome.getPath();
130 Node userProfile; // = userHome.getNode(ARGEO_PROFILE);
131 if (userHome.hasNode(ARGEO_PROFILE)) {
132 userProfile = userHome.getNode(ARGEO_PROFILE);
133 } else {
134 throw new ArgeoException("We should never reach this point");
135 // userProfile = userHome.addNode(ARGEO_PROFILE);
136 // userProfile.addMixin(NodeType.MIX_TITLE);
137 // userProfile.addMixin(NodeType.MIX_CREATED);
138 // userProfile.addMixin(NodeType.MIX_LAST_MODIFIED);
139 }
140
141 for (String jcrProperty : propertyToAttributes.keySet())
142 ldapToJcr(userProfile, jcrProperty, ctx);
143
144 // assign default values
145 if (!userProfile.hasProperty(Property.JCR_DESCRIPTION))
146 userProfile.setProperty(Property.JCR_DESCRIPTION, "");
147 if (!userProfile.hasProperty(Property.JCR_TITLE))
148 userProfile.setProperty(Property.JCR_TITLE, userProfile
149 .getProperty(ARGEO_FIRST_NAME).getString()
150 + " "
151 + userProfile.getProperty(ARGEO_LAST_NAME).getString());
152
153 session.save();
154 if (log.isTraceEnabled())
155 log.trace("Mapped " + ctx.getDn() + " to " + userProfile);
156 return userHomePath;
157 } catch (Exception e) {
158 JcrUtils.discardQuietly(session);
159 throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
160 }
161 }
162
163 public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
164 if (!(user instanceof JcrUserDetails))
165 throw new ArgeoException("Unsupported user details: "
166 + user.getClass());
167
168 ctx.setAttributeValues("objectClass", userClasses);
169 ctx.setAttributeValue(usernameAttribute, user.getUsername());
170 ctx.setAttributeValue(passwordAttribute,
171 encodePassword(user.getPassword()));
172
173 final JcrUserDetails jcrUserDetails = (JcrUserDetails) user;
174 try {
175 Node userProfile = session.getNode(jcrUserDetails.getHomePath()
176 + '/' + ARGEO_PROFILE);
177 for (String jcrProperty : propertyToAttributes.keySet())
178 jcrToLdap(userProfile, jcrProperty, ctx);
179
180 if (log.isTraceEnabled())
181 log.trace("Mapped " + userProfile + " to " + ctx.getDn());
182 } catch (RepositoryException e) {
183 throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
184 }
185 }
186
187 protected String encodePassword(String password) {
188 if (!password.startsWith("{")) {
189 byte[] salt = new byte[16];
190 random.nextBytes(salt);
191 return passwordEncoder.encodePassword(password, salt);
192 } else {
193 return password;
194 }
195 }
196
197 protected void ldapToJcr(Node userProfile, String jcrProperty,
198 DirContextOperations ctx) {
199 try {
200 String ldapAttribute;
201 if (propertyToAttributes.containsKey(jcrProperty))
202 ldapAttribute = propertyToAttributes.get(jcrProperty);
203 else
204 throw new ArgeoException(
205 "No LDAP attribute mapped for JCR proprty "
206 + jcrProperty);
207
208 String value = ctx.getStringAttribute(ldapAttribute);
209 if (value == null)
210 return;
211 userProfile.setProperty(jcrProperty, value);
212 } catch (Exception e) {
213 throw new ArgeoException("Cannot map JCR property " + jcrProperty
214 + " from LDAP", e);
215 }
216 }
217
218 protected void jcrToLdap(Node userProfile, String jcrProperty,
219 DirContextOperations ctx) {
220 try {
221 String ldapAttribute;
222 if (propertyToAttributes.containsKey(jcrProperty))
223 ldapAttribute = propertyToAttributes.get(jcrProperty);
224 else
225 throw new ArgeoException(
226 "No LDAP attribute mapped for JCR proprty "
227 + jcrProperty);
228
229 // fix issue with empty 'sn' in LDAP
230 if (ldapAttribute.equals("sn")
231 && (!userProfile.hasProperty(jcrProperty) || userProfile
232 .getProperty(jcrProperty).getString().trim()
233 .equals("")))
234 userProfile.setProperty(jcrProperty, "empty");
235
236 if (ldapAttribute.equals("description")) {
237 String value = userProfile.getProperty(jcrProperty).getString();
238 if (value.trim().equals(""))
239 return;
240 }
241
242 if (!userProfile.hasProperty(jcrProperty))
243 return;
244 String value = userProfile.getProperty(jcrProperty).getString();
245
246 ctx.setAttributeValue(ldapAttribute, value);
247 } catch (Exception e) {
248 throw new ArgeoException("Cannot map JCR property " + jcrProperty
249 + " from LDAP", e);
250 }
251 }
252
253 public void setPropertyToAttributes(Map<String, String> propertyToAttributes) {
254 this.propertyToAttributes = propertyToAttributes;
255 }
256
257 public void setSystemExecutor(Executor systemExecutor) {
258 this.systemExecutor = systemExecutor;
259 }
260
261 public void setHomeBasePath(String homeBasePath) {
262 this.homeBasePath = homeBasePath;
263 }
264
265 public void setUsernameAttribute(String usernameAttribute) {
266 this.usernameAttribute = usernameAttribute;
267 }
268
269 public void setPasswordAttribute(String passwordAttribute) {
270 this.passwordAttribute = passwordAttribute;
271 }
272
273 public void setUserClasses(String[] userClasses) {
274 this.userClasses = userClasses;
275 }
276
277 public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
278 this.passwordEncoder = passwordEncoder;
279 }
280
281 public void setSession(Session session) {
282 this.session = session;
283 }
284
285 }