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