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