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