]> 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.concurrent.Executor;
10
11 import javax.jcr.Node;
12 import javax.jcr.Property;
13 import javax.jcr.RepositoryException;
14 import javax.jcr.Session;
15 import javax.jcr.nodetype.NodeType;
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 final StringBuffer userHomePathT = new StringBuffer("");
68 Runnable action = new Runnable() {
69 public void run() {
70 String userHomepath = mapLdapToJcr(username, ctx);
71 userHomePathT.append(userHomepath);
72 }
73 };
74
75 if (SecurityContextHolder.getContext().getAuthentication() == null) {
76 // authentication
77 try {
78 systemExecutor.execute(action);
79 } finally {
80 JcrUtils.logoutQuietly(session);
81 }
82 } else {
83 // authenticated user
84 action.run();
85 }
86
87 // password
88 byte[] arr = (byte[]) ctx
89 .getAttributeSortedStringSet(passwordAttribute).first();
90 JcrUserDetails userDetails = new JcrUserDetails(
91 userHomePathT.toString(), username, new String(arr), true,
92 true, true, true, authorities);
93 // erase password
94 Arrays.fill(arr, (byte) 0);
95 return userDetails;
96 }
97
98 /** @return path to the user home node */
99 protected String mapLdapToJcr(String username, DirContextOperations ctx) {
100 try {
101 Node userHome = JcrUtils.getUserHome(session, username);
102 if (userHome == null)
103 userHome = JcrUtils.createUserHome(session, homeBasePath,
104 username);
105 String userHomePath = userHome.getPath();
106 Node userProfile = userHome.getNode(ARGEO_PROFILE);
107 if (userHome.hasNode(ARGEO_PROFILE)) {
108 userProfile = userHome.getNode(ARGEO_PROFILE);
109 } else {
110 userProfile = userHome.addNode(ARGEO_PROFILE);
111 userProfile.addMixin(NodeType.MIX_TITLE);
112 userProfile.addMixin(NodeType.MIX_CREATED);
113 userProfile.addMixin(NodeType.MIX_LAST_MODIFIED);
114 }
115
116 for (String jcrProperty : propertyToAttributes.keySet())
117 ldapToJcr(userProfile, jcrProperty, ctx);
118
119 // assign default values
120 if (!userProfile.hasProperty(Property.JCR_DESCRIPTION))
121 userProfile.setProperty(Property.JCR_DESCRIPTION, "");
122 if (!userProfile.hasProperty(Property.JCR_TITLE))
123 userProfile.setProperty(Property.JCR_TITLE, userProfile
124 .getProperty(ARGEO_FIRST_NAME).getString()
125 + " "
126 + userProfile.getProperty(ARGEO_LAST_NAME).getString());
127
128 session.save();
129 if (log.isTraceEnabled())
130 log.trace("Mapped " + ctx.getDn() + " to " + userProfile);
131 return userHomePath;
132 } catch (Exception e) {
133 JcrUtils.discardQuietly(session);
134 throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
135 }
136 }
137
138 public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
139 if (!(user instanceof JcrUserDetails))
140 throw new ArgeoException("Unsupported user details: "
141 + user.getClass());
142
143 ctx.setAttributeValues("objectClass", userClasses);
144 ctx.setAttributeValue(usernameAttribute, user.getUsername());
145 ctx.setAttributeValue(passwordAttribute,
146 encodePassword(user.getPassword()));
147
148 final JcrUserDetails jcrUserDetails = (JcrUserDetails) user;
149 try {
150 Node userProfile = session.getNode(jcrUserDetails.getHomePath()
151 + '/' + ARGEO_PROFILE);
152 for (String jcrProperty : propertyToAttributes.keySet())
153 jcrToLdap(userProfile, jcrProperty, ctx);
154
155 if (log.isTraceEnabled())
156 log.trace("Mapped " + userProfile + " to " + ctx.getDn());
157 } catch (RepositoryException e) {
158 throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
159 }
160 }
161
162 protected String encodePassword(String password) {
163 if (!password.startsWith("{")) {
164 byte[] salt = new byte[16];
165 random.nextBytes(salt);
166 return passwordEncoder.encodePassword(password, salt);
167 } else {
168 return password;
169 }
170 }
171
172 protected void ldapToJcr(Node userProfile, String jcrProperty,
173 DirContextOperations ctx) {
174 try {
175 String ldapAttribute;
176 if (propertyToAttributes.containsKey(jcrProperty))
177 ldapAttribute = propertyToAttributes.get(jcrProperty);
178 else
179 throw new ArgeoException(
180 "No LDAP attribute mapped for JCR proprty "
181 + jcrProperty);
182
183 String value = ctx.getStringAttribute(ldapAttribute);
184 if (value == null)
185 return;
186 userProfile.setProperty(jcrProperty, value);
187 } catch (Exception e) {
188 throw new ArgeoException("Cannot map JCR property " + jcrProperty
189 + " from LDAP", e);
190 }
191 }
192
193 protected void jcrToLdap(Node userProfile, String jcrProperty,
194 DirContextOperations ctx) {
195 try {
196 String ldapAttribute;
197 if (propertyToAttributes.containsKey(jcrProperty))
198 ldapAttribute = propertyToAttributes.get(jcrProperty);
199 else
200 throw new ArgeoException(
201 "No LDAP attribute mapped for JCR proprty "
202 + jcrProperty);
203
204 // fix issue with empty 'sn' in LDAP
205 if (ldapAttribute.equals("sn")
206 && (!userProfile.hasProperty(jcrProperty) || userProfile
207 .getProperty(jcrProperty).getString().trim()
208 .equals("")))
209 userProfile.setProperty(jcrProperty, "empty");
210
211 if (!userProfile.hasProperty(jcrProperty))
212 return;
213 String value = userProfile.getProperty(jcrProperty).getString();
214
215 ctx.setAttributeValue(ldapAttribute, value);
216 } catch (Exception e) {
217 throw new ArgeoException("Cannot map JCR property " + jcrProperty
218 + " from LDAP", e);
219 }
220 }
221
222 public void setPropertyToAttributes(Map<String, String> propertyToAttributes) {
223 this.propertyToAttributes = propertyToAttributes;
224 }
225
226 public void setSystemExecutor(Executor systemExecutor) {
227 this.systemExecutor = systemExecutor;
228 }
229
230 public void setHomeBasePath(String homeBasePath) {
231 this.homeBasePath = homeBasePath;
232 }
233
234 public void setUsernameAttribute(String usernameAttribute) {
235 this.usernameAttribute = usernameAttribute;
236 }
237
238 public void setPasswordAttribute(String passwordAttribute) {
239 this.passwordAttribute = passwordAttribute;
240 }
241
242 public void setUserClasses(String[] userClasses) {
243 this.userClasses = userClasses;
244 }
245
246 public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
247 this.passwordEncoder = passwordEncoder;
248 }
249
250 public void setSession(Session session) {
251 this.session = session;
252 }
253
254 }