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