]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
Standardise user admin unit tests
[lgpl/argeo-commons.git] / org.argeo.security.core / src / org / argeo / osgi / useradmin / AbstractUserDirectory.java
1 package org.argeo.osgi.useradmin;
2
3 import static org.argeo.osgi.useradmin.LdifName.inetOrgPerson;
4 import static org.argeo.osgi.useradmin.LdifName.objectClass;
5 import static org.argeo.osgi.useradmin.LdifName.organizationalPerson;
6 import static org.argeo.osgi.useradmin.LdifName.person;
7 import static org.argeo.osgi.useradmin.LdifName.top;
8
9 import java.io.File;
10 import java.net.URI;
11 import java.net.URISyntaxException;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Dictionary;
15 import java.util.Enumeration;
16 import java.util.Hashtable;
17 import java.util.Iterator;
18 import java.util.List;
19
20 import javax.naming.InvalidNameException;
21 import javax.naming.directory.Attributes;
22 import javax.naming.directory.BasicAttribute;
23 import javax.naming.directory.BasicAttributes;
24 import javax.naming.ldap.LdapName;
25 import javax.naming.ldap.Rdn;
26 import javax.transaction.SystemException;
27 import javax.transaction.Transaction;
28 import javax.transaction.TransactionManager;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.osgi.framework.Filter;
33 import org.osgi.framework.FrameworkUtil;
34 import org.osgi.framework.InvalidSyntaxException;
35 import org.osgi.service.useradmin.Authorization;
36 import org.osgi.service.useradmin.Role;
37 import org.osgi.service.useradmin.User;
38 import org.osgi.service.useradmin.UserAdmin;
39
40 /** Base class for a {@link UserDirectory}. */
41 abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
42 private final static Log log = LogFactory.getLog(AbstractUserDirectory.class);
43
44 private final Hashtable<String, Object> properties;
45 private final String baseDn;
46 private final String userObjectClass;
47 private final String groupObjectClass;
48
49 private final boolean readOnly;
50 private final URI uri;
51
52 private UserAdmin externalRoles;
53 private List<String> indexedUserProperties = Arrays
54 .asList(new String[] { LdifName.uid.name(), LdifName.mail.name(), LdifName.cn.name() });
55
56 private String memberAttributeId = "member";
57 private List<String> credentialAttributeIds = Arrays.asList(new String[] { LdifName.userPassword.name() });
58
59 private TransactionManager transactionManager;
60 private WcXaResource xaResource = new WcXaResource(this);
61
62 AbstractUserDirectory(Dictionary<String, ?> props) {
63 properties = new Hashtable<String, Object>();
64 for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
65 String key = keys.nextElement();
66 properties.put(key, props.get(key));
67 }
68
69 String uriStr = UserAdminConf.uri.getValue(properties);
70 if (uriStr == null)
71 uri = null;
72 else
73 try {
74 uri = new URI(uriStr);
75 } catch (URISyntaxException e) {
76 throw new UserDirectoryException("Badly formatted URI " + uriStr, e);
77 }
78
79 baseDn = UserAdminConf.baseDn.getValue(properties).toString();
80 String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
81 if (readOnlyStr == null) {
82 readOnly = readOnlyDefault(uri);
83 properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
84 } else
85 readOnly = new Boolean(readOnlyStr);
86
87 userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
88 groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
89 }
90
91 /** Returns the groups this user is a direct member of. */
92 protected abstract List<LdapName> getDirectGroups(LdapName dn);
93
94 protected abstract Boolean daoHasRole(LdapName dn);
95
96 protected abstract DirectoryUser daoGetRole(LdapName key);
97
98 protected abstract List<DirectoryUser> doGetRoles(Filter f);
99
100 public void init() {
101
102 }
103
104 public void destroy() {
105
106 }
107
108 boolean isEditing() {
109 return xaResource.wc() != null;
110 }
111
112 protected UserDirectoryWorkingCopy getWorkingCopy() {
113 UserDirectoryWorkingCopy wc = xaResource.wc();
114 if (wc == null)
115 return null;
116 return wc;
117 }
118
119 void checkEdit() {
120 Transaction transaction;
121 try {
122 transaction = transactionManager.getTransaction();
123 } catch (SystemException e) {
124 throw new UserDirectoryException("Cannot get transaction", e);
125 }
126 if (transaction == null)
127 throw new UserDirectoryException("A transaction needs to be active in order to edit");
128 if (xaResource.wc() == null) {
129 try {
130 transaction.enlistResource(xaResource);
131 } catch (Exception e) {
132 throw new UserDirectoryException("Cannot enlist " + xaResource, e);
133 }
134 } else {
135 }
136 }
137
138 List<Role> getAllRoles(DirectoryUser user) {
139 List<Role> allRoles = new ArrayList<Role>();
140 if (user != null) {
141 collectRoles(user, allRoles);
142 allRoles.add(user);
143 } else
144 collectAnonymousRoles(allRoles);
145 return allRoles;
146 }
147
148 private void collectRoles(DirectoryUser user, List<Role> allRoles) {
149 for (LdapName groupDn : getDirectGroups(user.getDn())) {
150 // TODO check for loops
151 DirectoryUser group = doGetRole(groupDn);
152 allRoles.add(group);
153 collectRoles(group, allRoles);
154 }
155 }
156
157 private void collectAnonymousRoles(List<Role> allRoles) {
158 // TODO gather anonymous roles
159 }
160
161 // USER ADMIN
162 @Override
163 public Role getRole(String name) {
164 return doGetRole(toDn(name));
165 }
166
167 protected DirectoryUser doGetRole(LdapName dn) {
168 UserDirectoryWorkingCopy wc = getWorkingCopy();
169 DirectoryUser user = daoGetRole(dn);
170 if (wc != null) {
171 if (user == null && wc.getNewUsers().containsKey(dn))
172 user = wc.getNewUsers().get(dn);
173 else if (wc.getDeletedUsers().containsKey(dn))
174 user = null;
175 }
176 return user;
177 }
178
179 @SuppressWarnings("unchecked")
180 @Override
181 public Role[] getRoles(String filter) throws InvalidSyntaxException {
182 UserDirectoryWorkingCopy wc = getWorkingCopy();
183 Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
184 List<DirectoryUser> res = doGetRoles(f);
185 if (wc != null) {
186 for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
187 DirectoryUser user = it.next();
188 LdapName dn = user.getDn();
189 if (wc.getDeletedUsers().containsKey(dn))
190 it.remove();
191 }
192 for (DirectoryUser user : wc.getNewUsers().values()) {
193 if (f == null || f.match(user.getProperties()))
194 res.add(user);
195 }
196 // no need to check modified users,
197 // since doGetRoles was already based on the modified attributes
198 }
199 return res.toArray(new Role[res.size()]);
200 }
201
202 @Override
203 public User getUser(String key, String value) {
204 // TODO check value null or empty
205 List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>(getIndexedUserProperties().size());
206 if (key != null) {
207 doGetUser(key, value, collectedUsers);
208 } else {
209 // try dn
210 DirectoryUser user = null;
211 try {
212 user = (DirectoryUser) getRole(value);
213 if (user != null)
214 collectedUsers.add(user);
215 } catch (Exception e) {
216 // silent
217 }
218 // try all indexes
219 for (String attr : getIndexedUserProperties())
220 doGetUser(attr, value, collectedUsers);
221 }
222 if (collectedUsers.size() == 1)
223 return collectedUsers.get(0);
224 else if (collectedUsers.size() > 1)
225 log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" : "") + value);
226 return null;
227 }
228
229 protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
230 try {
231 Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
232 List<DirectoryUser> users = doGetRoles(f);
233 collectedUsers.addAll(users);
234 } catch (InvalidSyntaxException e) {
235 throw new UserDirectoryException("Cannot get user with " + key + "=" + value, e);
236 }
237 }
238
239 @Override
240 public Authorization getAuthorization(User user) {
241 return new LdifAuthorization((DirectoryUser) user, getAllRoles((DirectoryUser) user));
242 }
243
244 @Override
245 public Role createRole(String name, int type) {
246 checkEdit();
247 UserDirectoryWorkingCopy wc = getWorkingCopy();
248 LdapName dn = toDn(name);
249 if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
250 throw new UserDirectoryException("Already a role " + name);
251 BasicAttributes attrs = new BasicAttributes(true);
252 // attrs.put(LdifName.dn.name(), dn.toString());
253 Rdn nameRdn = dn.getRdn(dn.size() - 1);
254 // TODO deal with multiple attr RDN
255 attrs.put(nameRdn.getType(), nameRdn.getValue());
256 if (wc.getDeletedUsers().containsKey(dn)) {
257 wc.getDeletedUsers().remove(dn);
258 wc.getModifiedUsers().put(dn, attrs);
259 } else {
260 wc.getModifiedUsers().put(dn, attrs);
261 DirectoryUser newRole = newRole(dn, type, attrs);
262 wc.getNewUsers().put(dn, newRole);
263 }
264 return getRole(name);
265 }
266
267 protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
268 LdifUser newRole;
269 BasicAttribute objClass = new BasicAttribute(objectClass.name());
270 if (type == Role.USER) {
271 String userObjClass = newUserObjectClass(dn);
272 objClass.add(userObjClass);
273 if (inetOrgPerson.name().equals(userObjClass)) {
274 objClass.add(organizationalPerson.name());
275 objClass.add(person.name());
276 } else if (organizationalPerson.name().equals(userObjClass)) {
277 objClass.add(person.name());
278 }
279 objClass.add(top.name());
280 attrs.put(objClass);
281 newRole = new LdifUser(this, dn, attrs);
282 } else if (type == Role.GROUP) {
283 String groupObjClass = getGroupObjectClass();
284 objClass.add(groupObjClass);
285 // objClass.add(LdifName.extensibleObject.name());
286 objClass.add(top.name());
287 attrs.put(objClass);
288 newRole = new LdifGroup(this, dn, attrs);
289 } else
290 throw new UserDirectoryException("Unsupported type " + type);
291 return newRole;
292 }
293
294 @Override
295 public boolean removeRole(String name) {
296 checkEdit();
297 UserDirectoryWorkingCopy wc = getWorkingCopy();
298 LdapName dn = toDn(name);
299 boolean actuallyDeleted;
300 if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
301 DirectoryUser user = (DirectoryUser) getRole(name);
302 wc.getDeletedUsers().put(dn, user);
303 actuallyDeleted = true;
304 } else {// just removing from groups (e.g. system roles)
305 actuallyDeleted = false;
306 }
307 for (LdapName groupDn : getDirectGroups(dn)) {
308 DirectoryUser group = doGetRole(groupDn);
309 group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
310 }
311 return actuallyDeleted;
312 }
313
314 // TRANSACTION
315 protected void prepare(UserDirectoryWorkingCopy wc) {
316
317 }
318
319 protected void commit(UserDirectoryWorkingCopy wc) {
320
321 }
322
323 protected void rollback(UserDirectoryWorkingCopy wc) {
324
325 }
326
327 // UTILITIES
328 protected LdapName toDn(String name) {
329 try {
330 return new LdapName(name);
331 } catch (InvalidNameException e) {
332 throw new UserDirectoryException("Badly formatted name", e);
333 }
334 }
335
336 // GETTERS
337 String getMemberAttributeId() {
338 return memberAttributeId;
339 }
340
341 List<String> getCredentialAttributeIds() {
342 return credentialAttributeIds;
343 }
344
345 protected URI getUri() {
346 return uri;
347 }
348
349 protected List<String> getIndexedUserProperties() {
350 return indexedUserProperties;
351 }
352
353 protected void setIndexedUserProperties(List<String> indexedUserProperties) {
354 this.indexedUserProperties = indexedUserProperties;
355 }
356
357 private static boolean readOnlyDefault(URI uri) {
358 if (uri == null)
359 return true;
360 if (uri.getScheme().equals("file")) {
361 File file = new File(uri);
362 if (file.exists())
363 return !file.canWrite();
364 else
365 return !file.getParentFile().canWrite();
366 }
367 return true;
368 }
369
370 public boolean isReadOnly() {
371 return readOnly;
372 }
373
374 UserAdmin getExternalRoles() {
375 return externalRoles;
376 }
377
378 public String getBaseDn() {
379 return baseDn;
380 }
381
382 /** dn can be null, in that case a default should be returned. */
383 protected String getUserObjectClass() {
384 return userObjectClass;
385 }
386
387 protected String newUserObjectClass(LdapName dn) {
388 return getUserObjectClass();
389 }
390
391 protected String getGroupObjectClass() {
392 return groupObjectClass;
393 }
394
395 public Dictionary<String, Object> getProperties() {
396 return properties;
397 }
398
399 public void setExternalRoles(UserAdmin externalRoles) {
400 this.externalRoles = externalRoles;
401 }
402
403 public void setTransactionManager(TransactionManager transactionManager) {
404 this.transactionManager = transactionManager;
405 }
406
407 public WcXaResource getXaResource() {
408 return xaResource;
409 }
410
411 }